odfdo

odfdo

Python library for OpenDocument format (ODF)

logo

odfdo is a Python3 library implementing the ISO/IEC 26300 OpenDocument Format standard.

Project: https://github.com/jdum/odfdo

Author: jerome.dumonteil@gmail.com

License: Apache License, Version 2.0

odfdo is a derivative work of the former lpod-python project.

Installation

Installation from Pypi (recommended):

pip install odfdo

Installation from sources (requiring setuptools):

pip install .

After installation from sources, you can check everything is working (some requirements: pytest, Pillow, ...):

pytest

The tests should run for a few seconds or minutes and issue no error.

Usage

from odfdo import Document, Paragraph

doc = Document('text')
doc.body.append(Paragraph("Hello world!"))
doc.save("hello.odt")

tl;dr

'Intended Audience :: Developers'

Documentation

There is no detailed documentation or tutorial, but:

  • the recipes folder contains more than 50 working sample scripts,
  • the doc folder contains an auto generated documentation.

When installing odfdo, a few scripts are installed:

  • odfdo-diff: show a diff between two .odt document.
  • odfdo-folder: convert standard ODF file to folder and files, and reverse.
  • odfdo-show: dump text from an ODF file to the standard output, and optionally styles and meta informations.
  • odfdo-styles: command line interface tool to manipulate styles of ODF files.
  • odfdo-replace: find a pattern (regex) in an ODF file and replace by some string.
  • odfdo-highlight: highlight the text matching a pattern (regex) in an ODF file.
  • odfdo-headers: print the headers of an ODF file.

About styles: the best way to apply style is by merging styles from a template document into your generated document (See odfdo-styles script). Styles are a complex matter in ODF, so trying to generate styles programmatically is not recommended.

Limitations

odfdo is intended to facilitate the generation of ODF documents, nevertheless a basic knowledge of the ODF format is necessary.

ODF document rendering can vary greatly from software to software. Especially the "styles" of the document allow an adaptation of the rendering for a particular software.

The best (only ?) way to apply style is by merging styles from a template document into your generated document.

Related project

I you work on .ods files (spreadsheet), you may be interested by these scripts that use this library to parse/generate .ods files: https://github.com/jdum/odsgenerator and https://github.com/jdum/odsparsator

Changes from former lpod library

lpod-python was written in 2009-2010 as a Python 2.x library, see: https://github.com/lpod/lpod-python

odfdo is an adaptation of this former project. odfdo main changes from lpod:

  • odfdo requires Python version 3.9 to 3.12. For Python 3.6 to 3.8 see previous releases.
  • API change: more pythonic.
  • include recipes.
  • use Apache 2.0 license.
  1# Copyright 2018-2024 Jérôme Dumonteil
  2# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend.
  3#
  4# Licensed under the Apache License, Version 2.0 (the "License");
  5# you may not use this file except in compliance with the License.
  6# You may obtain a copy of the License at
  7#
  8#     http://www.apache.org/licenses/LICENSE-2.0
  9#
 10# Unless required by applicable law or agreed to in writing, software
 11# distributed under the License is distributed on an "AS IS" BASIS,
 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13# See the License for the specific language governing permissions and
 14# limitations under the License.
 15#
 16#
 17# Authors (odfdo project): jerome.dumonteil@gmail.com
 18# The odfdo project is a derivative work of the lpod-python project:
 19# https://github.com/lpod/lpod-python
 20# Authors: David Versmisse <david.versmisse@itaapy.com>
 21#          Hervé Cauwelier <herve@itaapy.com>
 22#          Romain Gauthier <romain@itaapy.com>
 23"""
 24.. include:: ../README.md
 25"""
 26
 27__all__ = [
 28    "AnimPar",
 29    "AnimSeq",
 30    "AnimTransFilter",
 31    "Annotation",
 32    "AnnotationEnd",
 33    "BackgroundImage",
 34    "Bookmark",
 35    "BookmarkEnd",
 36    "BookmarkStart",
 37    "Cell",
 38    "ChangeInfo",
 39    "Column",
 40    "ConnectorShape",
 41    "Container",
 42    "Content",
 43    "Content",
 44    "Document",
 45    "DrawFillImage",
 46    "DrawGroup",
 47    "DrawImage",
 48    "DrawPage",
 49    "Element",
 50    "ElementTyped",
 51    "EllipseShape",
 52    "FIRST_CHILD",
 53    "Frame",
 54    "Header",
 55    "HeaderRows",
 56    "IndexTitle",
 57    "IndexTitleTemplate",
 58    "LAST_CHILD",
 59    "LineBreak",
 60    "LineShape",
 61    "Link",
 62    "List",
 63    "ListItem",
 64    "Manifest",
 65    "Meta",
 66    "NEXT_SIBLING",
 67    "NamedRange",
 68    "Note",
 69    "PREV_SIBLING",
 70    "PageBreak",
 71    "Paragraph",
 72    "RectangleShape",
 73    "Reference",
 74    "ReferenceMark",
 75    "ReferenceMarkEnd",
 76    "ReferenceMarkStart",
 77    "Row",
 78    "RowGroup",
 79    "Section",
 80    "Spacer",
 81    "Span",
 82    "Style",
 83    "Styles",
 84    "TOC",
 85    "Tab",
 86    "TabStopStyle",
 87    "Table",
 88    "Text",
 89    "TextChange",
 90    "TextChangeEnd",
 91    "TextChangeStart",
 92    "TextChangedRegion",
 93    "TextDeletion",
 94    "TextFormatChange",
 95    "TextInsertion",
 96    "TocEntryTemplate",
 97    "TrackedChanges",
 98    "UserDefined",
 99    "UserFieldDecl",
100    "UserFieldDecls",
101    "UserFieldGet",
102    "UserFieldInput",
103    "VarChapter",
104    "VarCreationDate",
105    "VarCreationTime",
106    "VarDate",
107    "VarDecl",
108    "VarDecls",
109    "VarDescription",
110    "VarFileName",
111    "VarGet",
112    "VarInitialCreator",
113    "VarKeywords",
114    "VarPageCount",
115    "VarPageNumber",
116    "VarSet",
117    "VarSubject",
118    "VarTime",
119    "VarTitle",
120    "XmlPart",
121    "__version__",
122    "create_table_cell_style",
123    "default_boolean_style",
124    "default_currency_style",
125    "default_date_style",
126    "default_frame_position_style",
127    "default_number_style",
128    "default_percentage_style",
129    "default_time_style",
130    "default_toc_level_style",
131    "hex2rgb",
132    "hexa_color",
133    "make_table_cell_border_string",
134    "rgb2hex",
135]
136
137
138from .bookmark import Bookmark, BookmarkEnd, BookmarkStart
139from .cell import Cell
140from .container import Container
141from .content import Content
142from .document import Document
143from .draw_page import DrawPage
144from .element import FIRST_CHILD, LAST_CHILD, NEXT_SIBLING, PREV_SIBLING, Element, Text
145from .element_typed import ElementTyped
146from .frame import Frame, default_frame_position_style
147from .header import Header
148from .header_rows import HeaderRows
149from .image import DrawFillImage, DrawImage
150from .link import Link
151from .list import List, ListItem
152from .manifest import Manifest
153from .meta import Meta
154from .note import Annotation, AnnotationEnd, Note
155from .paragraph import LineBreak, PageBreak, Paragraph, Spacer, Span, Tab
156from .reference import Reference, ReferenceMark, ReferenceMarkEnd, ReferenceMarkStart
157from .section import Section
158from .shapes import ConnectorShape, DrawGroup, EllipseShape, LineShape, RectangleShape
159from .smil import AnimPar, AnimSeq, AnimTransFilter
160from .style import (
161    BackgroundImage,
162    Style,
163    create_table_cell_style,
164    default_boolean_style,
165    default_currency_style,
166    default_date_style,
167    default_number_style,
168    default_percentage_style,
169    default_time_style,
170    make_table_cell_border_string,
171)
172from .styles import Styles
173from .table import Column, NamedRange, Row, RowGroup, Table
174from .toc import (
175    TOC,
176    IndexTitle,
177    IndexTitleTemplate,
178    TabStopStyle,
179    TocEntryTemplate,
180    default_toc_level_style,
181)
182from .tracked_changes import (
183    ChangeInfo,
184    TextChange,
185    TextChangedRegion,
186    TextChangeEnd,
187    TextChangeStart,
188    TextDeletion,
189    TextFormatChange,
190    TextInsertion,
191    TrackedChanges,
192)
193from .utils import hex2rgb, hexa_color, rgb2hex
194from .variable import (
195    UserDefined,
196    UserFieldDecl,
197    UserFieldDecls,
198    UserFieldGet,
199    UserFieldInput,
200    VarChapter,
201    VarCreationDate,
202    VarCreationTime,
203    VarDate,
204    VarDecl,
205    VarDecls,
206    VarDescription,
207    VarFileName,
208    VarGet,
209    VarInitialCreator,
210    VarKeywords,
211    VarPageCount,
212    VarPageNumber,
213    VarSet,
214    VarSubject,
215    VarTime,
216    VarTitle,
217)
218from .version import __version__
219from .xmlpart import XmlPart
class AnimPar(odfdo.Element):
32class AnimPar(Element):
33    """A container for SMIL Presentation Animations.
34
35    Arguments:
36
37        presentation_node_type -- default, on-click, with-previous,
38                                  after-previous, timing-root, main-sequence
39                                  and interactive-sequence
40
41        smil_begin -- indefinite, 10s, [id].click, [id].begin
42    """
43
44    _tag = "anim:par"
45    _properties = (
46        PropDef("presentation_node_type", "presentation:node-type"),
47        PropDef("smil_begin", "smil:begin"),
48    )
49
50    def __init__(
51        self,
52        presentation_node_type: str | None = None,
53        smil_begin: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        super().__init__(**kwargs)
57        if self._do_init:
58            if presentation_node_type:
59                self.presentation_node_type = presentation_node_type
60            if smil_begin:
61                self.smil_begin = smil_begin

A container for SMIL Presentation Animations.

Arguments:

presentation_node_type -- default, on-click, with-previous,
                          after-previous, timing-root, main-sequence
                          and interactive-sequence

smil_begin -- indefinite, 10s, [id].click, [id].begin
AnimPar( presentation_node_type: str | None = None, smil_begin: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        presentation_node_type: str | None = None,
53        smil_begin: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        super().__init__(**kwargs)
57        if self._do_init:
58            if presentation_node_type:
59                self.presentation_node_type = presentation_node_type
60            if smil_begin:
61                self.smil_begin = smil_begin
presentation_node_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_begin: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnimSeq(odfdo.Element):
67class AnimSeq(Element):
68    """TA container for SMIL Presentation Animations. Animations
69    inside this block are executed after the slide has executed its initial
70    transition.
71
72    Arguments:
73
74        presentation_node_type -- default, on-click, with-previous,
75                                  after-previous, timing-root, main-sequence
76                                  and interactive-sequence
77    """
78
79    _tag = "anim:seq"
80    _properties = (PropDef("presentation_node_type", "presentation:node-type"),)
81
82    def __init__(
83        self,
84        presentation_node_type: str | None = None,
85        **kwargs: Any,
86    ) -> None:
87        super().__init__(**kwargs)
88        if self._do_init and presentation_node_type:
89            self.presentation_node_type = presentation_node_type

TA container for SMIL Presentation Animations. Animations inside this block are executed after the slide has executed its initial transition.

Arguments:

presentation_node_type -- default, on-click, with-previous,
                          after-previous, timing-root, main-sequence
                          and interactive-sequence
AnimSeq(presentation_node_type: str | None = None, **kwargs: Any)
82    def __init__(
83        self,
84        presentation_node_type: str | None = None,
85        **kwargs: Any,
86    ) -> None:
87        super().__init__(**kwargs)
88        if self._do_init and presentation_node_type:
89            self.presentation_node_type = presentation_node_type
presentation_node_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnimTransFilter(odfdo.Element):
 95class AnimTransFilter(Element):
 96    """
 97    Class to make a beautiful transition between two frames.
 98
 99    Arguments:
100      smil_dur -- XXX complete me
101
102      smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/
103                    smil-transitions.html#TransitionEffects-Appendix
104                                    to get a list of all types/subtypes
105
106      smil_direction -- forward, reverse
107
108      smil_fadeColor -- forward, reverse
109
110      smil_mode -- in, out
111    """
112
113    _tag = "anim:transitionFilter"
114    _properties = (
115        PropDef("smil_dur", "smil:dur"),
116        PropDef("smil_type", "smil:type"),
117        PropDef("smil_subtype", "smil:subtype"),
118        PropDef("smil_direction", "smil:direction"),
119        PropDef("smil_fadeColor", "smil:fadeColor"),
120        PropDef("smil_mode", "smil:mode"),
121    )
122
123    def __init__(
124        self,
125        smil_dur: str | None = None,
126        smil_type: str | None = None,
127        smil_subtype: str | None = None,
128        smil_direction: str | None = None,
129        smil_fadeColor: str | None = None,
130        smil_mode: str | None = None,
131        **kwargs: Any,
132    ) -> None:
133        super().__init__(**kwargs)
134        if self._do_init:
135            if smil_dur:
136                self.smil_dur = smil_dur
137            if smil_type:
138                self.smil_type = smil_type
139            if smil_subtype:
140                self.smil_subtype = smil_subtype
141            if smil_direction:
142                self.smil_direction = smil_direction
143            if smil_fadeColor:
144                self.smil_fadeColor = smil_fadeColor
145            if smil_mode:
146                self.smil_mode = smil_mode

Class to make a beautiful transition between two frames.

Arguments: smil_dur -- XXX complete me

smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ smil-transitions.html#TransitionEffects-Appendix to get a list of all types/subtypes

smil_direction -- forward, reverse

smil_fadeColor -- forward, reverse

smil_mode -- in, out

AnimTransFilter( smil_dur: str | None = None, smil_type: str | None = None, smil_subtype: str | None = None, smil_direction: str | None = None, smil_fadeColor: str | None = None, smil_mode: str | None = None, **kwargs: Any)
123    def __init__(
124        self,
125        smil_dur: str | None = None,
126        smil_type: str | None = None,
127        smil_subtype: str | None = None,
128        smil_direction: str | None = None,
129        smil_fadeColor: str | None = None,
130        smil_mode: str | None = None,
131        **kwargs: Any,
132    ) -> None:
133        super().__init__(**kwargs)
134        if self._do_init:
135            if smil_dur:
136                self.smil_dur = smil_dur
137            if smil_type:
138                self.smil_type = smil_type
139            if smil_subtype:
140                self.smil_subtype = smil_subtype
141            if smil_direction:
142                self.smil_direction = smil_direction
143            if smil_fadeColor:
144                self.smil_fadeColor = smil_fadeColor
145            if smil_mode:
146                self.smil_mode = smil_mode
smil_dur: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_subtype: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_direction: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_fadeColor: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_mode: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Annotation(odfdo.Element):
147class Annotation(Element):
148    """Annotation element credited to the given creator with the
149    given text, optionally dated (current date by default).
150    If name not provided and some parent is provided, the name is
151    autogenerated.
152
153    Arguments:
154
155        text -- str or odf_element
156
157        creator -- str
158
159        date -- datetime
160
161        name -- str
162
163        parent -- Element
164    """
165
166    _tag = "office:annotation"
167    _properties = (
168        PropDef("name", "office:name"),
169        PropDef("note_id", "text:id"),
170    )
171
172    def __init__(
173        self,
174        text_or_element: Element | str | None = None,
175        creator: str | None = None,
176        date: datetime | None = None,
177        name: str | None = None,
178        parent: Element | None = None,
179        **kwargs: Any,
180    ) -> None:
181        # fixme : use offset
182        # TODO allow paragraph and text styles
183        super().__init__(**kwargs)
184
185        if self._do_init:
186            self.note_body = text_or_element  # type:ignore
187            if creator:
188                self.dc_creator = creator
189            if date is None:
190                date = datetime.now()
191            self.dc_date = date
192            if not name:
193                name = get_unique_office_name(parent)
194                self.name = name
195
196    @property
197    def note_body(self) -> str:
198        return self.text_content
199
200    @note_body.setter
201    def note_body(self, text_or_element: Element | str | None) -> None:
202        if text_or_element is None:
203            self.text_content = ""
204        elif isinstance(text_or_element, str):
205            self.text_content = text_or_element
206        elif isinstance(text_or_element, Element):
207            self.clear()
208            self.append(text_or_element)
209        else:
210            raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"')
211
212    @property
213    def start(self) -> Element:
214        """Return self."""
215        return self
216
217    @property
218    def end(self) -> Element | None:
219        """Return the corresponding annotation-end tag or None."""
220        name = self.name
221        parent = self.parent
222        if parent is None:
223            raise ValueError("Can't find end tag: no parent available")
224        body = self.document_body
225        if not body:
226            body = parent
227        return body.get_annotation_end(name=name)
228
229    def get_annotated(
230        self,
231        as_text: bool = False,
232        no_header: bool = True,
233        clean: bool = True,
234    ) -> Element | list | str | None:
235        """Returns the annotated content from an annotation.
236
237        If no content exists (single position annotation or annotation-end not
238        found), returns [] (or "" if text flag is True).
239        If as_text is True: returns the text content.
240        If clean is True: suppress unwanted tags (deletions marks, ...)
241        If no_header is True: existing text:h are changed in text:p
242        By default: returns a list of odf_element, cleaned and without headers.
243
244        Arguments:
245
246            as_text -- boolean
247
248            clean -- boolean
249
250            no_header -- boolean
251
252        Return: list or Element or text or None
253        """
254        end = self.end
255        if end is None:
256            if as_text:
257                return ""
258            return None
259        body = self.document_body
260        if not body:
261            body = self.root
262        return body.get_between(
263            self, end, as_text=as_text, no_header=no_header, clean=clean
264        )
265
266    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
267        """Delete the given element from the XML tree. If no element is given,
268        "self" is deleted. The XML library may allow to continue to use an
269        element now "orphan" as long as you have a reference to it.
270
271        For Annotation : delete the annotation-end tag if exists.
272
273        Arguments:
274
275            child -- Element or None
276        """
277        if child is not None:  # act like normal delete
278            super().delete(child)
279            return
280        end = self.end
281        if end:
282            end.delete()
283        # act like normal delete
284        super().delete()
285
286    def check_validity(self) -> None:
287        if not self.note_body:
288            raise ValueError("Annotation must have a body")
289        if not self.dc_creator:
290            raise ValueError("Annotation must have a creator")
291        if not self.dc_date:
292            self.dc_date = datetime.now()

Annotation element credited to the given creator with the given text, optionally dated (current date by default). If name not provided and some parent is provided, the name is autogenerated.

Arguments:

text -- str or odf_element

creator -- str

date -- datetime

name -- str

parent -- Element
Annotation( text_or_element: Element | str | None = None, creator: str | None = None, date: datetime.datetime | None = None, name: str | None = None, parent: Element | None = None, **kwargs: Any)
172    def __init__(
173        self,
174        text_or_element: Element | str | None = None,
175        creator: str | None = None,
176        date: datetime | None = None,
177        name: str | None = None,
178        parent: Element | None = None,
179        **kwargs: Any,
180    ) -> None:
181        # fixme : use offset
182        # TODO allow paragraph and text styles
183        super().__init__(**kwargs)
184
185        if self._do_init:
186            self.note_body = text_or_element  # type:ignore
187            if creator:
188                self.dc_creator = creator
189            if date is None:
190                date = datetime.now()
191            self.dc_date = date
192            if not name:
193                name = get_unique_office_name(parent)
194                self.name = name
note_body: str
196    @property
197    def note_body(self) -> str:
198        return self.text_content
start: Element
212    @property
213    def start(self) -> Element:
214        """Return self."""
215        return self

Return self.

end: Element | None
217    @property
218    def end(self) -> Element | None:
219        """Return the corresponding annotation-end tag or None."""
220        name = self.name
221        parent = self.parent
222        if parent is None:
223            raise ValueError("Can't find end tag: no parent available")
224        body = self.document_body
225        if not body:
226            body = parent
227        return body.get_annotation_end(name=name)

Return the corresponding annotation-end tag or None.

def get_annotated( self, as_text: bool = False, no_header: bool = True, clean: bool = True) -> Element | list | str | None:
229    def get_annotated(
230        self,
231        as_text: bool = False,
232        no_header: bool = True,
233        clean: bool = True,
234    ) -> Element | list | str | None:
235        """Returns the annotated content from an annotation.
236
237        If no content exists (single position annotation or annotation-end not
238        found), returns [] (or "" if text flag is True).
239        If as_text is True: returns the text content.
240        If clean is True: suppress unwanted tags (deletions marks, ...)
241        If no_header is True: existing text:h are changed in text:p
242        By default: returns a list of odf_element, cleaned and without headers.
243
244        Arguments:
245
246            as_text -- boolean
247
248            clean -- boolean
249
250            no_header -- boolean
251
252        Return: list or Element or text or None
253        """
254        end = self.end
255        if end is None:
256            if as_text:
257                return ""
258            return None
259        body = self.document_body
260        if not body:
261            body = self.root
262        return body.get_between(
263            self, end, as_text=as_text, no_header=no_header, clean=clean
264        )

Returns the annotated content from an annotation.

If no content exists (single position annotation or annotation-end not found), returns [] (or "" if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of odf_element, cleaned and without headers.

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text or None

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
266    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
267        """Delete the given element from the XML tree. If no element is given,
268        "self" is deleted. The XML library may allow to continue to use an
269        element now "orphan" as long as you have a reference to it.
270
271        For Annotation : delete the annotation-end tag if exists.
272
273        Arguments:
274
275            child -- Element or None
276        """
277        if child is not None:  # act like normal delete
278            super().delete(child)
279            return
280        end = self.end
281        if end:
282            end.delete()
283        # act like normal delete
284        super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For Annotation : delete the annotation-end tag if exists.

Arguments:

child -- Element or None
def check_validity(self) -> None:
286    def check_validity(self) -> None:
287        if not self.note_body:
288            raise ValueError("Annotation must have a body")
289        if not self.dc_creator:
290            raise ValueError("Annotation must have a creator")
291        if not self.dc_date:
292            self.dc_date = datetime.now()
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
note_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnnotationEnd(odfdo.Element):
298class AnnotationEnd(Element):
299    """AnnotationEnd: the "office:annotation-end" element may be used to
300    define the end of a text range of document content that spans element
301    boundaries. In that case, an "office:annotation" element shall precede
302    the "office:annotation-end" element. Both elements shall have the same
303    value for their office:name attribute. The "office:annotation-end" element
304    shall be preceded by an "office:annotation" element that has the same
305    value for its office:name attribute as the "office:annotation-end"
306    element. An "office:annotation-end" element without a preceding
307    "office:annotation" element that has the same name assigned is ignored.
308    """
309
310    _tag = "office:annotation-end"
311    _properties = (PropDef("name", "office:name"),)
312
313    def __init__(
314        self,
315        annotation: Element | None = None,
316        name: str | None = None,
317        **kwargs: Any,
318    ) -> None:
319        """Initialize an AnnotationEnd element. Either annotation or name must be
320        provided to have proper reference for the annotation-end.
321
322        Arguments:
323
324            annotation -- odf_annotation element
325
326            name -- str
327        """
328        # fixme : use offset
329        # TODO allow paragraph and text styles
330        super().__init__(**kwargs)
331        if self._do_init:
332            if annotation:
333                name = annotation.name  # type: ignore
334            if not name:
335                raise ValueError("Annotation-end must have a name")
336            self.name = name
337
338    @property
339    def start(self) -> Element | None:
340        """Return the corresponding annotation starting tag or None."""
341        name = self.name
342        parent = self.parent
343        if parent is None:
344            raise ValueError("Can't find start tag: no parent available")
345        body = self.document_body
346        if not body:
347            body = parent
348        return body.get_annotation(name=name)
349
350    @property
351    def end(self) -> Element:
352        """Return self."""
353        return self

AnnotationEnd: the "office:annotation-end" element may be used to define the end of a text range of document content that spans element boundaries. In that case, an "office:annotation" element shall precede the "office:annotation-end" element. Both elements shall have the same value for their office:name attribute. The "office:annotation-end" element shall be preceded by an "office:annotation" element that has the same value for its office:name attribute as the "office:annotation-end" element. An "office:annotation-end" element without a preceding "office:annotation" element that has the same name assigned is ignored.

AnnotationEnd( annotation: Element | None = None, name: str | None = None, **kwargs: Any)
313    def __init__(
314        self,
315        annotation: Element | None = None,
316        name: str | None = None,
317        **kwargs: Any,
318    ) -> None:
319        """Initialize an AnnotationEnd element. Either annotation or name must be
320        provided to have proper reference for the annotation-end.
321
322        Arguments:
323
324            annotation -- odf_annotation element
325
326            name -- str
327        """
328        # fixme : use offset
329        # TODO allow paragraph and text styles
330        super().__init__(**kwargs)
331        if self._do_init:
332            if annotation:
333                name = annotation.name  # type: ignore
334            if not name:
335                raise ValueError("Annotation-end must have a name")
336            self.name = name

Initialize an AnnotationEnd element. Either annotation or name must be provided to have proper reference for the annotation-end.

Arguments:

annotation -- odf_annotation element

name -- str
start: Element | None
338    @property
339    def start(self) -> Element | None:
340        """Return the corresponding annotation starting tag or None."""
341        name = self.name
342        parent = self.parent
343        if parent is None:
344            raise ValueError("Can't find start tag: no parent available")
345        body = self.document_body
346        if not body:
347            body = parent
348        return body.get_annotation(name=name)

Return the corresponding annotation starting tag or None.

end: Element
350    @property
351    def end(self) -> Element:
352        """Return self."""
353        return self

Return self.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BackgroundImage(odfdo.Style, odfdo.DrawImage):
 999class BackgroundImage(Style, DrawImage):
1000    _tag = "style:background-image"
1001    _properties: tuple[PropDef, ...] = (
1002        PropDef("name", "style:name"),
1003        PropDef("display_name", "style:display-name"),
1004        PropDef("svg_font_family", "svg:font-family"),
1005        PropDef("font_family_generic", "style:font-family-generic"),
1006        PropDef("font_pitch", "style:font-pitch"),
1007        PropDef("position", "style:position", "background-image"),
1008        PropDef("repeat", "style:repeat", "background-image"),
1009        PropDef("opacity", "draw:opacity", "background-image"),
1010        PropDef("filter", "style:filter-name", "background-image"),
1011        PropDef("text_style", "text:style-name"),
1012    )
1013
1014    def __init__(
1015        self,
1016        name: str | None = None,
1017        display_name: str | None = None,
1018        position: str | None = None,
1019        repeat: str | None = None,
1020        opacity: str | None = None,
1021        filter: str | None = None,  # noqa: A002
1022        # Every other property
1023        **kwargs: Any,
1024    ):
1025        kwargs["family"] = "background-image"
1026        super().__init__(**kwargs)
1027        if self._do_init:
1028            kwargs.pop("tag", None)
1029            kwargs.pop("tag_or_elem", None)
1030            self.family = "background-image"
1031            if name:
1032                self.name = name
1033            if display_name:
1034                self.display_name = display_name
1035            if position:
1036                self.position = position
1037            if repeat:
1038                self.position = repeat
1039            if opacity:
1040                self.position = opacity
1041            if filter:
1042                self.position = filter
1043            # Every other properties
1044            for prop in BackgroundImage._properties:
1045                if prop.name in kwargs:
1046                    self.set_style_attribute(prop.attr, kwargs[prop.name])

Style class for all these tags:

'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...

BackgroundImage( name: str | None = None, display_name: str | None = None, position: str | None = None, repeat: str | None = None, opacity: str | None = None, filter: str | None = None, **kwargs: Any)
1014    def __init__(
1015        self,
1016        name: str | None = None,
1017        display_name: str | None = None,
1018        position: str | None = None,
1019        repeat: str | None = None,
1020        opacity: str | None = None,
1021        filter: str | None = None,  # noqa: A002
1022        # Every other property
1023        **kwargs: Any,
1024    ):
1025        kwargs["family"] = "background-image"
1026        super().__init__(**kwargs)
1027        if self._do_init:
1028            kwargs.pop("tag", None)
1029            kwargs.pop("tag_or_elem", None)
1030            self.family = "background-image"
1031            if name:
1032                self.name = name
1033            if display_name:
1034                self.display_name = display_name
1035            if position:
1036                self.position = position
1037            if repeat:
1038                self.position = repeat
1039            if opacity:
1040                self.position = opacity
1041            if filter:
1042                self.position = filter
1043            # Every other properties
1044            for prop in BackgroundImage._properties:
1045                if prop.name in kwargs:
1046                    self.set_style_attribute(prop.attr, kwargs[prop.name])

Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.

The display name is the name the user sees in an office application.

The parent_style is the name of the style this style will inherit from.

To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.

Arguments:

family -- 'paragraph', 'text', 'section', 'table', 'table-column',
          'table-row', 'table-cell', 'table-page', 'chart',
          'drawing-page', 'graphic', 'presentation',
          'control', 'ruby', 'list', 'number', 'page-layout'
          'font-face', or 'master-page'

name -- str

display_name -- str

parent_style -- str

area -- str

'text' Properties:

italic -- bool

bold -- bool

'paragraph' Properties:

master_page -- str

'master-page' Properties:

page_layout -- str

next_style -- str

'table-cell' Properties:

border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'

padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

'table-row' Properties:

height -- str, e.g. '5cm'

use_optimal_height -- bool

'table-column' Properties:

width -- str, e.g. '5cm'

break_before -- 'page', 'column' or 'auto'

break_after -- 'page', 'column' or 'auto'
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
display_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
svg_font_family: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_family_generic: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_pitch: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
repeat: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
opacity: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
filter: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
text_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Style
family
get_properties
set_properties
del_properties
set_background
get_level_style
set_level_style
get_header_style
set_header_style
get_page_header
set_page_header
set_font
page_layout
next_style
parent_style
master_page
style_type
leader_style
leader_text
style_position
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
DrawImage
url
type
show
actuate
filter_name
class Bookmark(odfdo.Element):
31class Bookmark(Element):
32    """
33    Bookmark class for ODF "text:bookmark"
34
35    Arguments:
36
37        name -- str
38    """
39
40    _tag = "text:bookmark"
41    _properties = (PropDef("name", "text:name"),)
42
43    def __init__(self, name: str = "", **kwargs: Any) -> None:
44        super().__init__(**kwargs)
45        if self._do_init:
46            self.name = name

Bookmark class for ODF "text:bookmark"

Arguments:

name -- str
Bookmark(name: str = '', **kwargs: Any)
43    def __init__(self, name: str = "", **kwargs: Any) -> None:
44        super().__init__(**kwargs)
45        if self._do_init:
46            self.name = name
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BookmarkEnd(odfdo.Element):
73class BookmarkEnd(Element):
74    """
75    BookmarkEnd class for ODF "text:bookmark-end"
76
77    Arguments:
78
79        name -- str
80    """
81
82    _tag = "text:bookmark-end"
83    _properties = (PropDef("name", "text:name"),)
84
85    def __init__(self, name: str = "", **kwargs: Any) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.name = name

BookmarkEnd class for ODF "text:bookmark-end"

Arguments:

name -- str
BookmarkEnd(name: str = '', **kwargs: Any)
85    def __init__(self, name: str = "", **kwargs: Any) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.name = name
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BookmarkStart(odfdo.Element):
52class BookmarkStart(Element):
53    """
54    BookmarkStart class for ODF "text:bookmark-start"
55
56    Arguments:
57
58        name -- str
59    """
60
61    _tag = "text:bookmark-start"
62    _properties = (PropDef("name", "text:name"),)
63
64    def __init__(self, name: str = "", **kwargs: Any) -> None:
65        super().__init__(**kwargs)
66        if self._do_init:
67            self.name = name

BookmarkStart class for ODF "text:bookmark-start"

Arguments:

name -- str
BookmarkStart(name: str = '', **kwargs: Any)
64    def __init__(self, name: str = "", **kwargs: Any) -> None:
65        super().__init__(**kwargs)
66        if self._do_init:
67            self.name = name
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Cell(odfdo.ElementTyped):
 44class Cell(ElementTyped):
 45    """ "table:table-cell" table cell element."""
 46
 47    _tag = "table:table-cell"
 48    _caching = True
 49
 50    def __init__(
 51        self,
 52        value: Any = None,
 53        text: str | None = None,
 54        cell_type: str | None = None,
 55        currency: str | None = None,
 56        formula: str | None = None,
 57        repeated: int | None = None,
 58        style: str | None = None,
 59        **kwargs: Any,
 60    ) -> None:
 61        """Create a cell element containing the given value. The textual
 62        representation is automatically formatted but can be provided. Cell
 63        type can be deduced as well, unless the number is a percentage or
 64        currency. If cell type is "currency", the currency must be given.
 65        The cell can be repeated on the given number of columns.
 66
 67        Arguments:
 68
 69            value -- bool, int, float, Decimal, date, datetime, str,
 70                     timedelta
 71
 72            text -- str
 73
 74            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
 75                         'string' or 'time'
 76
 77            currency -- three-letter str
 78
 79            repeated -- int
 80
 81            style -- str
 82        """
 83        super().__init__(**kwargs)
 84        self.x = None
 85        self.y = None
 86        if self._do_init:
 87            self.set_value(
 88                value,
 89                text=text,
 90                cell_type=cell_type,
 91                currency=currency,
 92                formula=formula,
 93            )
 94            if repeated and repeated > 1:
 95                self.repeated = repeated
 96            if style is not None:
 97                self.style = style
 98
 99    @property
100    def clone(self) -> Cell:
101        clone = Element.clone.fget(self)  # type: ignore
102        clone.y = self.y
103        clone.x = self.x
104        if hasattr(self, "_tmap"):
105            if hasattr(self, "_rmap"):
106                clone._rmap = self._rmap[:]
107            clone._tmap = self._tmap[:]
108            clone._cmap = self._cmap[:]
109        return clone
110
111    @property
112    def value(
113        self,
114    ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None:
115        """Set / get the value of the cell. The type is read from the
116        'office:value-type' attribute of the cell. When setting the value,
117        the type of the value will determine the new value_type of the cell.
118
119        Warning: use this method for boolean, float or string only.
120        """
121        value_type = self.get_attribute_string("office:value-type")
122        if value_type == "boolean":
123            return self.get_attribute("office:boolean-value")
124        if value_type in {"float", "percentage", "currency"}:
125            value_decimal = Decimal(str(self.get_attribute_string("office:value")))
126            # Return 3 instead of 3.0 if possible
127            if int(value_decimal) == value_decimal:
128                return int(value_decimal)
129            return value_decimal
130        if value_type == "date":
131            value_str = str(self.get_attribute_string("office:date-value"))
132            if "T" in value_str:
133                return DateTime.decode(value_str)
134            return Date.decode(value_str)
135        if value_type == "time":
136            return Duration.decode(str(self.get_attribute_string("office:time-value")))
137        if value_type == "string":
138            value = self.get_attribute_string("office:string-value")
139            if value is not None:
140                return value
141            value_list = []
142            for para in self.get_elements("text:p"):
143                value_list.append(para.text_recursive)
144            return "\n".join(value_list)
145        return None
146
147    @value.setter
148    def value(self, value: str | bytes | bool | int | Float | Decimal | None) -> None:
149        self.clear()
150        if value is None:
151            return
152        if isinstance(value, (str, bytes)):
153            if isinstance(value, bytes):
154                value = bytes_to_str(value)
155            self.set_attribute("office:value-type", "string")
156            self.set_attribute("office:string-value", value)
157            self.text = value
158            return
159        if value is True or value is False:
160            self.set_attribute("office:value-type", "boolean")
161            value_bool = Boolean.encode(value)
162            self.set_attribute("office:boolean-value", value_bool)
163            self.text = value_bool
164            return
165        if isinstance(value, (int, Float, Decimal)):
166            self.set_attribute("office:value-type", "float")
167            value_str = str(value)
168            self.set_attribute("office:value", value_str)
169            self.text = value_str
170            return
171        raise TypeError(f"Unknown value type, try with set_value() : {value!r}")
172
173    @property
174    def float(self) -> Float:
175        """Set / get the value of the cell as a float (or 0.0)."""
176        for tag in ("office:value", "office:string-value", "office:boolean-value"):
177            read_attr = self.get_attribute(tag)
178            if isinstance(read_attr, str):
179                with contextlib.suppress(ValueError, TypeError):
180                    return Float(read_attr)
181        return 0.0
182
183    @float.setter
184    def float(self, value: str | Float | int | Decimal) -> None:
185        try:
186            value_float = Float(value)
187        except (ValueError, TypeError):
188            value_float = 0.0
189        value_str = str(value_float)
190        self.clear()
191        self.set_attribute("office:value", value_str)
192        self.set_attribute("office:value-type", "float")
193        self.text = value_str
194
195    @property
196    def string(self) -> str:
197        """Set / get the value of the cell as a string (or '')."""
198        value = self.get_attribute_string("office:string-value")
199        if isinstance(value, str):
200            return value
201        return ""
202
203    @string.setter
204    def string(
205        self,
206        value: str | bytes | int | Float | Decimal | bool | None,  # type: ignore
207    ) -> None:
208        self.clear()
209        if value is None:
210            value_str = ""
211        else:
212            value_str = str(value)
213        self.set_attribute("office:value-type", "string")
214        self.set_attribute("office:string-value", value_str)
215        self.text = value_str
216
217    def set_value(
218        self,
219        value: (
220            str  # type: ignore
221            | bytes
222            | Float
223            | int
224            | Decimal
225            | bool
226            | datetime
227            | date
228            | timedelta
229            | None
230        ),
231        text: str | None = None,
232        cell_type: str | None = None,
233        currency: str | None = None,
234        formula: str | None = None,
235    ) -> None:
236        """Set the cell state from the Python value type.
237
238        Text is how the cell is displayed. Cell type is guessed,
239        unless provided.
240
241        For monetary values, provide the name of the currency.
242
243        Arguments:
244
245            value -- Python type
246
247            text -- str
248
249            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
250                        'currency' or 'percentage'
251
252            currency -- str
253        """
254        self.clear()
255        text = self.set_value_and_type(
256            value=value,
257            text=text,
258            value_type=cell_type,
259            currency=currency,
260        )
261        if text is not None:
262            self.text_content = text
263        if formula is not None:
264            self.formula = formula
265
266    @property
267    def type(self) -> str | None:
268        """Get / set the type of the cell: boolean, float, date, string
269        or time.
270
271        Return: str | None
272        """
273        return self.get_attribute_string("office:value-type")
274
275    @type.setter
276    def type(self, cell_type: str) -> None:
277        self.set_attribute("office:value-type", cell_type)
278
279    @property
280    def currency(self) -> str | None:
281        """Get / set the currency used for monetary values.
282
283        Return: str | None
284        """
285        return self.get_attribute_string("office:currency")
286
287    @currency.setter
288    def currency(self, currency: str) -> None:
289        self.set_attribute("office:currency", currency)
290
291    def _set_repeated(self, repeated: int | None) -> None:
292        """Internal only. Set the numnber of times the cell is repeated, or
293        None to delete. Without changing cache.
294        """
295        if repeated is None or repeated < 2:
296            with contextlib.suppress(KeyError):
297                self.del_attribute("table:number-columns-repeated")
298            return
299        self.set_attribute("table:number-columns-repeated", str(repeated))
300
301    @property
302    def repeated(self) -> int | None:
303        """Get / set the number of times the cell is repeated.
304
305        Always None when using the table API.
306
307        Return: int or None
308        """
309        repeated = self.get_attribute("table:number-columns-repeated")
310        if repeated is None:
311            return None
312        return int(repeated)
313
314    @repeated.setter
315    def repeated(self, repeated: int | None) -> None:
316        self._set_repeated(repeated)
317        # update cache
318        child: Element = self
319        while True:
320            # look for Row, parent may be group of rows
321            upper = child.parent
322            if not upper:
323                # lonely cell
324                return
325            # parent may be group of rows, not table
326            if isinstance(upper, Element) and upper._tag == "table:table-row":
327                break
328            child = upper
329        # fixme : need to optimize this
330        if isinstance(upper, Element) and upper._tag == "table:table-row":
331            upper._compute_row_cache()
332
333    @property
334    def style(self) -> str | None:
335        """Get / set the style of the cell itself.
336
337        Return: str | None
338        """
339        return self.get_attribute_string("table:style-name")
340
341    @style.setter
342    def style(self, style: str | Element) -> None:
343        self.set_style_attribute("table:style-name", style)
344
345    @property
346    def formula(self) -> str | None:
347        """Get / set the formula of the cell, or None if undefined.
348
349        The formula is not interpreted in any way.
350
351        Return: str | None
352        """
353        return self.get_attribute_string("table:formula")
354
355    @formula.setter
356    def formula(self, formula: str | None) -> None:
357        self.set_attribute("table:formula", formula)
358
359    def is_empty(self, aggressive: bool = False) -> bool:
360        if self.value is not None or self.children:
361            return False
362        if not aggressive and self.style is not None:
363            return False
364        return True
365
366    def _is_spanned(self) -> bool:
367        if self.tag == "table:covered-table-cell":
368            return True
369        if self.get_attribute("table:number-columns-spanned") is not None:
370            return True
371        if self.get_attribute("table:number-rows-spanned") is not None:
372            return True
373        return False

"table:table-cell" table cell element.

Cell( value: Any = None, text: str | None = None, cell_type: str | None = None, currency: str | None = None, formula: str | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        value: Any = None,
53        text: str | None = None,
54        cell_type: str | None = None,
55        currency: str | None = None,
56        formula: str | None = None,
57        repeated: int | None = None,
58        style: str | None = None,
59        **kwargs: Any,
60    ) -> None:
61        """Create a cell element containing the given value. The textual
62        representation is automatically formatted but can be provided. Cell
63        type can be deduced as well, unless the number is a percentage or
64        currency. If cell type is "currency", the currency must be given.
65        The cell can be repeated on the given number of columns.
66
67        Arguments:
68
69            value -- bool, int, float, Decimal, date, datetime, str,
70                     timedelta
71
72            text -- str
73
74            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
75                         'string' or 'time'
76
77            currency -- three-letter str
78
79            repeated -- int
80
81            style -- str
82        """
83        super().__init__(**kwargs)
84        self.x = None
85        self.y = None
86        if self._do_init:
87            self.set_value(
88                value,
89                text=text,
90                cell_type=cell_type,
91                currency=currency,
92                formula=formula,
93            )
94            if repeated and repeated > 1:
95                self.repeated = repeated
96            if style is not None:
97                self.style = style

Create a cell element containing the given value. The textual representation is automatically formatted but can be provided. Cell type can be deduced as well, unless the number is a percentage or currency. If cell type is "currency", the currency must be given. The cell can be repeated on the given number of columns.

Arguments:

value -- bool, int, float, Decimal, date, datetime, str,
         timedelta

text -- str

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

repeated -- int

style -- str
x
y
clone: Cell
 99    @property
100    def clone(self) -> Cell:
101        clone = Element.clone.fget(self)  # type: ignore
102        clone.y = self.y
103        clone.x = self.x
104        if hasattr(self, "_tmap"):
105            if hasattr(self, "_rmap"):
106                clone._rmap = self._rmap[:]
107            clone._tmap = self._tmap[:]
108            clone._cmap = self._cmap[:]
109        return clone
value: str | bool | int | float | decimal.Decimal | datetime.date | datetime.datetime | datetime.timedelta | None
111    @property
112    def value(
113        self,
114    ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None:
115        """Set / get the value of the cell. The type is read from the
116        'office:value-type' attribute of the cell. When setting the value,
117        the type of the value will determine the new value_type of the cell.
118
119        Warning: use this method for boolean, float or string only.
120        """
121        value_type = self.get_attribute_string("office:value-type")
122        if value_type == "boolean":
123            return self.get_attribute("office:boolean-value")
124        if value_type in {"float", "percentage", "currency"}:
125            value_decimal = Decimal(str(self.get_attribute_string("office:value")))
126            # Return 3 instead of 3.0 if possible
127            if int(value_decimal) == value_decimal:
128                return int(value_decimal)
129            return value_decimal
130        if value_type == "date":
131            value_str = str(self.get_attribute_string("office:date-value"))
132            if "T" in value_str:
133                return DateTime.decode(value_str)
134            return Date.decode(value_str)
135        if value_type == "time":
136            return Duration.decode(str(self.get_attribute_string("office:time-value")))
137        if value_type == "string":
138            value = self.get_attribute_string("office:string-value")
139            if value is not None:
140                return value
141            value_list = []
142            for para in self.get_elements("text:p"):
143                value_list.append(para.text_recursive)
144            return "\n".join(value_list)
145        return None

Set / get the value of the cell. The type is read from the 'office:value-type' attribute of the cell. When setting the value, the type of the value will determine the new value_type of the cell.

Warning: use this method for boolean, float or string only.

float: float
173    @property
174    def float(self) -> Float:
175        """Set / get the value of the cell as a float (or 0.0)."""
176        for tag in ("office:value", "office:string-value", "office:boolean-value"):
177            read_attr = self.get_attribute(tag)
178            if isinstance(read_attr, str):
179                with contextlib.suppress(ValueError, TypeError):
180                    return Float(read_attr)
181        return 0.0

Set / get the value of the cell as a float (or 0.0).

string: str
195    @property
196    def string(self) -> str:
197        """Set / get the value of the cell as a string (or '')."""
198        value = self.get_attribute_string("office:string-value")
199        if isinstance(value, str):
200            return value
201        return ""

Set / get the value of the cell as a string (or '').

def set_value( self, value: str | bytes | float | int | decimal.Decimal | bool | datetime.datetime | datetime.date | datetime.timedelta | None, text: str | None = None, cell_type: str | None = None, currency: str | None = None, formula: str | None = None) -> None:
217    def set_value(
218        self,
219        value: (
220            str  # type: ignore
221            | bytes
222            | Float
223            | int
224            | Decimal
225            | bool
226            | datetime
227            | date
228            | timedelta
229            | None
230        ),
231        text: str | None = None,
232        cell_type: str | None = None,
233        currency: str | None = None,
234        formula: str | None = None,
235    ) -> None:
236        """Set the cell state from the Python value type.
237
238        Text is how the cell is displayed. Cell type is guessed,
239        unless provided.
240
241        For monetary values, provide the name of the currency.
242
243        Arguments:
244
245            value -- Python type
246
247            text -- str
248
249            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
250                        'currency' or 'percentage'
251
252            currency -- str
253        """
254        self.clear()
255        text = self.set_value_and_type(
256            value=value,
257            text=text,
258            value_type=cell_type,
259            currency=currency,
260        )
261        if text is not None:
262            self.text_content = text
263        if formula is not None:
264            self.formula = formula

Set the cell state from the Python value type.

Text is how the cell is displayed. Cell type is guessed, unless provided.

For monetary values, provide the name of the currency.

Arguments:

value -- Python type

text -- str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
            'currency' or 'percentage'

currency -- str
type: str | None
266    @property
267    def type(self) -> str | None:
268        """Get / set the type of the cell: boolean, float, date, string
269        or time.
270
271        Return: str | None
272        """
273        return self.get_attribute_string("office:value-type")

Get / set the type of the cell: boolean, float, date, string or time.

Return: str | None

currency: str | None
279    @property
280    def currency(self) -> str | None:
281        """Get / set the currency used for monetary values.
282
283        Return: str | None
284        """
285        return self.get_attribute_string("office:currency")

Get / set the currency used for monetary values.

Return: str | None

repeated: int | None
301    @property
302    def repeated(self) -> int | None:
303        """Get / set the number of times the cell is repeated.
304
305        Always None when using the table API.
306
307        Return: int or None
308        """
309        repeated = self.get_attribute("table:number-columns-repeated")
310        if repeated is None:
311            return None
312        return int(repeated)

Get / set the number of times the cell is repeated.

Always None when using the table API.

Return: int or None

style: str | None
333    @property
334    def style(self) -> str | None:
335        """Get / set the style of the cell itself.
336
337        Return: str | None
338        """
339        return self.get_attribute_string("table:style-name")

Get / set the style of the cell itself.

Return: str | None

formula: str | None
345    @property
346    def formula(self) -> str | None:
347        """Get / set the formula of the cell, or None if undefined.
348
349        The formula is not interpreted in any way.
350
351        Return: str | None
352        """
353        return self.get_attribute_string("table:formula")

Get / set the formula of the cell, or None if undefined.

The formula is not interpreted in any way.

Return: str | None

def is_empty(self, aggressive: bool = False) -> bool:
359    def is_empty(self, aggressive: bool = False) -> bool:
360        if self.value is not None or self.children:
361            return False
362        if not aggressive and self.style is not None:
363            return False
364        return True

Check if the element is empty : no text, no children, no tail.

Return: Boolean

Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ChangeInfo(odfdo.Element):
 37class ChangeInfo(Element):
 38    """The "office:change-info" element represents who made a change and when.
 39    It may also contain a comment (one or more Paragrah "text:p" elements)
 40    on the change.
 41
 42    The comments available in the ChangeInfo are available through:
 43      - get_paragraphs and get_paragraph methods for actual Paragraph.
 44      - get_comments for a plain text version
 45
 46      Arguments:
 47
 48         creator -- str (or None)
 49
 50         date -- datetime (or None)
 51    """
 52
 53    _tag = "office:change-info"
 54
 55    def __init__(
 56        self,
 57        creator: str | None = None,
 58        date: datetime | None = None,
 59        **kwargs: Any,
 60    ) -> None:
 61        super().__init__(**kwargs)
 62        if self._do_init:
 63            self.set_dc_creator(creator)
 64            self.set_dc_date(date)
 65
 66    def set_dc_creator(self, creator: str | None = None) -> None:
 67        """Set the creator of the change. Default for creator is 'Unknown'.
 68
 69        Arguments:
 70
 71            creator -- str (or None)
 72        """
 73        element = self.get_element("dc:creator")
 74        if element is None:
 75            element = Element.from_tag("dc:creator")
 76            self.insert(element, xmlposition=FIRST_CHILD)
 77        if not creator:
 78            creator = "Unknown"
 79        element.text = creator
 80
 81    def set_dc_date(self, date: datetime | None = None) -> None:
 82        """Set the date of the change. If date is None, use current time.
 83
 84        Arguments:
 85
 86            date -- datetime (or None)
 87        """
 88        if date is None:
 89            date = datetime.now()
 90        dcdate = DateTime.encode(date)
 91        element = self.get_element("dc:date")
 92        if element is None:
 93            element = Element.from_tag("dc:date")
 94            self.insert(element, xmlposition=LAST_CHILD)
 95        element.text = dcdate
 96
 97    def get_comments(self, joined: bool = True) -> str | list[str]:
 98        """Get text content of the comments. If joined is True (default), the
 99        text of different paragraphs is concatenated, else a list of strings,
100        one per paragraph, is returned.
101
102        Arguments:
103
104            joined -- boolean (default is True)
105
106        Return: str or list of str.
107        """
108        content = self.get_paragraphs()
109        if content is None:
110            content = []
111        text = [para.get_formatted_text(simple=True) for para in content]  # type: ignore
112        if joined:
113            return "\n".join(text)
114        return text
115
116    def set_comments(self, text: str = "", replace: bool = True) -> None:
117        """Set the text content of the comments. If replace is True (default),
118        the new text replace old comments, else it is added at the end.
119
120        Arguments:
121
122            text -- str
123
124            replace -- boolean
125        """
126        if replace:
127            for para in self.get_paragraphs():
128                self.delete(para)
129        para = Paragraph()
130        para.append_plain_text(text)
131        self.insert(para, xmlposition=LAST_CHILD)

The "office:change-info" element represents who made a change and when. It may also contain a comment (one or more Paragrah "text:p" elements) on the change.

The comments available in the ChangeInfo are available through:

  • get_paragraphs and get_paragraph methods for actual Paragraph.
  • get_comments for a plain text version

Arguments:

 creator -- str (or None)

 date -- datetime (or None)
ChangeInfo( creator: str | None = None, date: datetime.datetime | None = None, **kwargs: Any)
55    def __init__(
56        self,
57        creator: str | None = None,
58        date: datetime | None = None,
59        **kwargs: Any,
60    ) -> None:
61        super().__init__(**kwargs)
62        if self._do_init:
63            self.set_dc_creator(creator)
64            self.set_dc_date(date)
def set_dc_creator(self, creator: str | None = None) -> None:
66    def set_dc_creator(self, creator: str | None = None) -> None:
67        """Set the creator of the change. Default for creator is 'Unknown'.
68
69        Arguments:
70
71            creator -- str (or None)
72        """
73        element = self.get_element("dc:creator")
74        if element is None:
75            element = Element.from_tag("dc:creator")
76            self.insert(element, xmlposition=FIRST_CHILD)
77        if not creator:
78            creator = "Unknown"
79        element.text = creator

Set the creator of the change. Default for creator is 'Unknown'.

Arguments:

creator -- str (or None)
def set_dc_date(self, date: datetime.datetime | None = None) -> None:
81    def set_dc_date(self, date: datetime | None = None) -> None:
82        """Set the date of the change. If date is None, use current time.
83
84        Arguments:
85
86            date -- datetime (or None)
87        """
88        if date is None:
89            date = datetime.now()
90        dcdate = DateTime.encode(date)
91        element = self.get_element("dc:date")
92        if element is None:
93            element = Element.from_tag("dc:date")
94            self.insert(element, xmlposition=LAST_CHILD)
95        element.text = dcdate

Set the date of the change. If date is None, use current time.

Arguments:

date -- datetime (or None)
def get_comments(self, joined: bool = True) -> str | list[str]:
 97    def get_comments(self, joined: bool = True) -> str | list[str]:
 98        """Get text content of the comments. If joined is True (default), the
 99        text of different paragraphs is concatenated, else a list of strings,
100        one per paragraph, is returned.
101
102        Arguments:
103
104            joined -- boolean (default is True)
105
106        Return: str or list of str.
107        """
108        content = self.get_paragraphs()
109        if content is None:
110            content = []
111        text = [para.get_formatted_text(simple=True) for para in content]  # type: ignore
112        if joined:
113            return "\n".join(text)
114        return text

Get text content of the comments. If joined is True (default), the text of different paragraphs is concatenated, else a list of strings, one per paragraph, is returned.

Arguments:

joined -- boolean (default is True)

Return: str or list of str.

def set_comments(self, text: str = '', replace: bool = True) -> None:
116    def set_comments(self, text: str = "", replace: bool = True) -> None:
117        """Set the text content of the comments. If replace is True (default),
118        the new text replace old comments, else it is added at the end.
119
120        Arguments:
121
122            text -- str
123
124            replace -- boolean
125        """
126        if replace:
127            for para in self.get_paragraphs():
128                self.delete(para)
129        para = Paragraph()
130        para.append_plain_text(text)
131        self.insert(para, xmlposition=LAST_CHILD)

Set the text content of the comments. If replace is True (default), the new text replace old comments, else it is added at the end.

Arguments:

text -- str

replace -- boolean
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Column(odfdo.Element):
164class Column(Element):
165    """ODF table column "table:table-column" """
166
167    _tag = "table:table-column"
168    _caching = True
169
170    def __init__(
171        self,
172        default_cell_style: str | None = None,
173        repeated: int | None = None,
174        style: str | None = None,
175        **kwargs: Any,
176    ) -> None:
177        """Create a column group element of the optionally given style. Cell
178        style can be set for the whole column. If the properties apply to
179        several columns, give the number of repeated columns.
180
181        Columns don't contain cells, just style information.
182
183        You don't generally have to create columns by hand, use the Table API.
184
185        Arguments:
186
187            default_cell_style -- str
188
189            repeated -- int
190
191            style -- str
192        """
193        super().__init__(**kwargs)
194        self.x = None
195        if self._do_init:
196            if default_cell_style:
197                self.set_default_cell_style(default_cell_style)
198            if repeated and repeated > 1:
199                self.repeated = repeated
200            if style:
201                self.style = style
202
203    @property
204    def clone(self) -> Column:
205        clone = Element.clone.fget(self)  # type: ignore
206        clone.x = self.x
207        if hasattr(self, "_tmap"):
208            if hasattr(self, "_rmap"):
209                clone._rmap = self._rmap[:]
210            clone._tmap = self._tmap[:]
211            clone._cmap = self._cmap[:]
212        return clone
213
214    def get_default_cell_style(self) -> str | None:
215        return self.get_attribute_string("table:default-cell-style-name")
216
217    def set_default_cell_style(self, style: Element | str) -> None:
218        self.set_style_attribute("table:default-cell-style-name", style)
219
220    def _set_repeated(self, repeated: int | None) -> None:
221        """Internal only. Set the number of times the column is repeated, or
222        None to delete it. Without changing cache.
223
224        Arguments:
225
226            repeated -- int or None
227        """
228        if repeated is None or repeated < 2:
229            with contextlib.suppress(KeyError):
230                self.del_attribute("table:number-columns-repeated")
231            return
232        self.set_attribute("table:number-columns-repeated", str(repeated))
233
234    @property
235    def repeated(self) -> int | None:
236        """Get /set the number of times the column is repeated.
237
238        Always None when using the table API.
239
240        Return: int or None
241        """
242        repeated = self.get_attribute("table:number-columns-repeated")
243        if repeated is None:
244            return None
245        return int(repeated)
246
247    @repeated.setter
248    def repeated(self, repeated: int | None) -> None:
249        self._set_repeated(repeated)
250        # update cache
251        current: Element = self
252        while True:
253            # look for Table, parent may be group of rows
254            upper = current.parent
255            if not upper:
256                # lonely column
257                return
258            # parent may be group of rows, not table
259            if isinstance(upper, Table):
260                break
261            current = upper
262        # fixme : need to optimize this
263        if isinstance(upper, Table):
264            upper._compute_table_cache()
265            if hasattr(self, "_cmap"):
266                del self._cmap[:]
267                self._cmap.extend(upper._cmap)
268            else:
269                self._cmap = upper._cmap
270
271    @property
272    def style(self) -> str | None:
273        """Get /set the style of the column itself.
274
275        Return: str
276        """
277        return self.get_attribute_string("table:style-name")
278
279    @style.setter
280    def style(self, style: str | Element) -> None:
281        self.set_style_attribute("table:style-name", style)

ODF table column "table:table-column"

Column( default_cell_style: str | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
170    def __init__(
171        self,
172        default_cell_style: str | None = None,
173        repeated: int | None = None,
174        style: str | None = None,
175        **kwargs: Any,
176    ) -> None:
177        """Create a column group element of the optionally given style. Cell
178        style can be set for the whole column. If the properties apply to
179        several columns, give the number of repeated columns.
180
181        Columns don't contain cells, just style information.
182
183        You don't generally have to create columns by hand, use the Table API.
184
185        Arguments:
186
187            default_cell_style -- str
188
189            repeated -- int
190
191            style -- str
192        """
193        super().__init__(**kwargs)
194        self.x = None
195        if self._do_init:
196            if default_cell_style:
197                self.set_default_cell_style(default_cell_style)
198            if repeated and repeated > 1:
199                self.repeated = repeated
200            if style:
201                self.style = style

Create a column group element of the optionally given style. Cell style can be set for the whole column. If the properties apply to several columns, give the number of repeated columns.

Columns don't contain cells, just style information.

You don't generally have to create columns by hand, use the Table API.

Arguments:

default_cell_style -- str

repeated -- int

style -- str
x
clone: Column
203    @property
204    def clone(self) -> Column:
205        clone = Element.clone.fget(self)  # type: ignore
206        clone.x = self.x
207        if hasattr(self, "_tmap"):
208            if hasattr(self, "_rmap"):
209                clone._rmap = self._rmap[:]
210            clone._tmap = self._tmap[:]
211            clone._cmap = self._cmap[:]
212        return clone
def get_default_cell_style(self) -> str | None:
214    def get_default_cell_style(self) -> str | None:
215        return self.get_attribute_string("table:default-cell-style-name")
def set_default_cell_style(self, style: Element | str) -> None:
217    def set_default_cell_style(self, style: Element | str) -> None:
218        self.set_style_attribute("table:default-cell-style-name", style)
repeated: int | None
234    @property
235    def repeated(self) -> int | None:
236        """Get /set the number of times the column is repeated.
237
238        Always None when using the table API.
239
240        Return: int or None
241        """
242        repeated = self.get_attribute("table:number-columns-repeated")
243        if repeated is None:
244            return None
245        return int(repeated)

Get /set the number of times the column is repeated.

Always None when using the table API.

Return: int or None

style: str | None
271    @property
272    def style(self) -> str | None:
273        """Get /set the style of the column itself.
274
275        Return: str
276        """
277        return self.get_attribute_string("table:style-name")

Get /set the style of the column itself.

Return: str

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ConnectorShape(odfdo.shapes.ShapeBase):
242class ConnectorShape(ShapeBase):
243    """Create a Connector shape.
244
245    Arguments:
246
247        style -- str
248
249        text_style -- str
250
251        draw_id -- str
252
253        layer -- str
254
255        connected_shapes -- (shape, shape)
256
257        glue_points -- (point, point)
258
259        p1 -- (str, str)
260
261        p2 -- (str, str)
262    """
263
264    _tag = "draw:connector"
265    _properties: tuple[PropDef, ...] = (
266        PropDef("start_shape", "draw:start-shape"),
267        PropDef("end_shape", "draw:end-shape"),
268        PropDef("start_glue_point", "draw:start-glue-point"),
269        PropDef("end_glue_point", "draw:end-glue-point"),
270        PropDef("x1", "svg:x1"),
271        PropDef("y1", "svg:y1"),
272        PropDef("x2", "svg:x2"),
273        PropDef("y2", "svg:y2"),
274    )
275
276    def __init__(
277        self,
278        style: str | None = None,
279        text_style: str | None = None,
280        draw_id: str | None = None,
281        layer: str | None = None,
282        connected_shapes: tuple | None = None,
283        glue_points: tuple | None = None,
284        p1: tuple | None = None,
285        p2: tuple | None = None,
286        **kwargs: Any,
287    ) -> None:
288        kwargs.update(
289            {
290                "style": style,
291                "text_style": text_style,
292                "draw_id": draw_id,
293                "layer": layer,
294            }
295        )
296        super().__init__(**kwargs)
297        if self._do_init:
298            if connected_shapes:
299                self.start_shape = connected_shapes[0].draw_id
300                self.end_shape = connected_shapes[1].draw_id
301            if glue_points:
302                self.start_glue_point = glue_points[0]
303                self.end_glue_point = glue_points[1]
304            if p1:
305                self.x1 = p1[0]
306                self.y1 = p1[1]
307            if p2:
308                self.x2 = p2[0]
309                self.y2 = p2[1]

Create a Connector shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

connected_shapes -- (shape, shape)

glue_points -- (point, point)

p1 -- (str, str)

p2 -- (str, str)
ConnectorShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, connected_shapes: tuple | None = None, glue_points: tuple | None = None, p1: tuple | None = None, p2: tuple | None = None, **kwargs: Any)
276    def __init__(
277        self,
278        style: str | None = None,
279        text_style: str | None = None,
280        draw_id: str | None = None,
281        layer: str | None = None,
282        connected_shapes: tuple | None = None,
283        glue_points: tuple | None = None,
284        p1: tuple | None = None,
285        p2: tuple | None = None,
286        **kwargs: Any,
287    ) -> None:
288        kwargs.update(
289            {
290                "style": style,
291                "text_style": text_style,
292                "draw_id": draw_id,
293                "layer": layer,
294            }
295        )
296        super().__init__(**kwargs)
297        if self._do_init:
298            if connected_shapes:
299                self.start_shape = connected_shapes[0].draw_id
300                self.end_shape = connected_shapes[1].draw_id
301            if glue_points:
302                self.start_glue_point = glue_points[0]
303                self.end_glue_point = glue_points[1]
304            if p1:
305                self.x1 = p1[0]
306                self.y1 = p1[1]
307            if p2:
308                self.x2 = p2[0]
309                self.y2 = p2[1]
start_shape: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
end_shape: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
start_glue_point: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
end_glue_point: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
x1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
x2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class Container:
 55class Container:
 56    """Representation of the ODF file."""
 57
 58    def __init__(self, path: Path | str | io.BytesIO | None = None) -> None:
 59        self.__parts: dict[str, bytes | None] = {}
 60        self.__parts_ts: dict[str, int] = {}
 61        self.__path_like: Path | str | io.BytesIO | None = None
 62        self.__packaging: str = "zip"
 63        self.path: Path | None = None  # or Path
 64        if path:
 65            self.open(path)
 66
 67    def __repr__(self) -> str:
 68        return f"<{self.__class__.__name__} type={self.mimetype} path={self.path}>"
 69
 70    def open(self, path_or_file: Path | str | io.BytesIO) -> None:
 71        """Load the content of an ODF file."""
 72        self.__path_like = path_or_file
 73        if isinstance(path_or_file, (str, Path)):
 74            self.path = Path(path_or_file)
 75            if not self.path.exists():
 76                raise FileNotFoundError(str(self.path))
 77        if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile(
 78            path_or_file
 79        ):
 80            self.__packaging = "zip"
 81            return self._read_zip()
 82        if self.path:
 83            is_folder = False
 84            with contextlib.suppress(OSError):
 85                is_folder = self.path.is_dir()
 86            if is_folder:
 87                self.__packaging = "folder"
 88                return self._read_folder()
 89        raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")
 90
 91    def _read_zip(self) -> None:
 92        if isinstance(self.__path_like, io.BytesIO):
 93            self.__path_like.seek(0)
 94        with ZipFile(self.__path_like) as zf:  # type: ignore
 95            mimetype = bytes_to_str(zf.read("mimetype"))
 96            if mimetype not in ODF_MIMETYPES:
 97                raise ValueError(f"Document of unknown type {mimetype}")
 98            self.__parts["mimetype"] = str_to_bytes(mimetype)
 99        if self.path is None:
100            if isinstance(self.__path_like, io.BytesIO):
101                self.__path_like.seek(0)
102            # read the full file at once and forget file
103            with ZipFile(self.__path_like) as zf:  # type: ignore
104                for name in zf.namelist():
105                    upath = normalize_path(name)
106                    self.__parts[upath] = zf.read(name)
107            self.__path_like = None
108
109    def _read_folder(self) -> None:
110        try:
111            mimetype, timestamp = self._get_folder_part("mimetype")
112        except OSError:
113            printwarn("Corrupted or not an OpenDocument folder (missing mimetype)")
114            mimetype = b""
115            timestamp = int(time.time())
116        if bytes_to_str(mimetype) not in ODF_MIMETYPES:
117            message = f"Document of unknown type {mimetype!r}, try with ODF Text."
118            printwarn(message)
119            self.__parts["mimetype"] = str_to_bytes(ODF_EXTENSIONS["odt"])
120            self.__parts_ts["mimetype"] = timestamp
121
122    def _parse_folder(self, folder: str) -> list[str]:
123        parts = []
124        if self.path is None:
125            raise ValueError("Document path is not defined")
126        root = self.path / folder
127        for path in root.iterdir():
128            if path.name.startswith("."):  # no hidden files
129                continue
130            relative_path = path.relative_to(self.path)
131            if path.is_file():
132                parts.append(relative_path.as_posix())
133            if path.is_dir():
134                sub_parts = self._parse_folder(str(relative_path))
135                if sub_parts:
136                    parts.extend(sub_parts)
137                else:
138                    # store leaf directories
139                    parts.append(relative_path.as_posix() + "/")
140        return parts
141
142    def _get_folder_parts(self) -> list[str]:
143        """Get the list of members in the ODF folder."""
144        return self._parse_folder("")
145
146    def _get_folder_part(self, name: str) -> tuple[bytes, int]:
147        """Get bytes of a part from the ODF folder, with timestamp."""
148        if self.path is None:
149            raise ValueError("Document path is not defined")
150        path = self.path / name
151        timestamp = int(path.stat().st_mtime)
152        if path.is_dir():
153            return (b"", timestamp)
154        return (path.read_bytes(), timestamp)
155
156    def _get_folder_part_timestamp(self, name: str) -> int:
157        if self.path is None:
158            raise ValueError("Document path is not defined")
159        path = self.path / name
160        try:
161            timestamp = int(path.stat().st_mtime)
162        except OSError:
163            timestamp = -1
164        return timestamp
165
166    def _get_zip_part(self, name: str) -> bytes | None:
167        """Get bytes of a part from the Zip ODF file. No cache."""
168        if self.path is None:
169            raise ValueError("Document path is not defined")
170        try:
171            with ZipFile(self.path) as zf:
172                upath = normalize_path(name)
173                self.__parts[upath] = zf.read(name)
174                return self.__parts[upath]
175        except BadZipfile:
176            return None
177
178    def _get_all_zip_part(self) -> None:
179        """Read all parts. No cache."""
180        if self.path is None:
181            raise ValueError("Document path is not defined")
182        try:
183            with ZipFile(self.path) as zf:
184                for name in zf.namelist():
185                    upath = normalize_path(name)
186                    self.__parts[upath] = zf.read(name)
187        except BadZipfile:
188            pass
189
190    def _save_zip(self, target: str | Path | io.BytesIO) -> None:
191        """Save a Zip ODF from the available parts."""
192        parts = self.__parts
193        with ZipFile(target, "w", compression=ZIP_DEFLATED) as filezip:
194            # Parts to save, except manifest at the end
195            part_names = list(parts.keys())
196            try:
197                part_names.remove(ODF_MANIFEST)
198            except ValueError:
199                printwarn(f"Missing '{ODF_MANIFEST}'")
200            # "Pretty-save" parts in some order
201            # mimetype requires to be first and uncompressed
202            mimetype = parts.get("mimetype")
203            if mimetype is None:
204                raise ValueError("Mimetype is not defined")
205            try:
206                filezip.writestr("mimetype", mimetype, ZIP_STORED)
207                part_names.remove("mimetype")
208            except (ValueError, KeyError):
209                printwarn("Missing 'mimetype'")
210            # XML parts
211            for path in ODF_CONTENT, ODF_META, ODF_SETTINGS, ODF_STYLES:
212                if path not in parts:
213                    printwarn(f"Missing '{path}'")
214                    continue
215                part = parts[path]
216                if part is None:
217                    continue
218                filezip.writestr(path, part)
219                part_names.remove(path)
220            # Everything else
221            for path in part_names:
222                data = parts[path]
223                if data is None:
224                    # Deleted
225                    continue
226                filezip.writestr(path, data)
227            # Manifest
228            with contextlib.suppress(KeyError):
229                part = parts[ODF_MANIFEST]
230                if part is not None:
231                    filezip.writestr(ODF_MANIFEST, part)
232
233    def _save_folder(self, folder: Path | str) -> None:
234        """Save a folder ODF from the available parts."""
235
236        def dump(part_path: str, content: bytes) -> None:
237            if part_path.endswith("/"):  # folder
238                is_folder = True
239                pure_path = PurePath(folder, part_path[:-1])
240            else:
241                is_folder = False
242                pure_path = PurePath(folder, part_path)
243            path = Path(pure_path)
244            if is_folder:
245                path.mkdir(parents=True, exist_ok=True)
246            else:
247                path.parent.mkdir(parents=True, exist_ok=True)
248                path.write_bytes(content)
249                path.chmod(0o666)
250
251        for part_path, data in self.__parts.items():
252            if data is None:
253                # Deleted
254                continue
255            dump(part_path, data)
256
257    # Public API
258
259    def get_parts(self) -> list[str]:
260        """Get the list of members."""
261        if not self.path:
262            # maybe a file like zip archive
263            return list(self.__parts.keys())
264        if self.__packaging == "zip":
265            parts = []
266            with ZipFile(self.path) as zf:
267                for name in zf.namelist():
268                    upath = normalize_path(name)
269                    parts.append(upath)
270            return parts
271        elif self.__packaging == "folder":
272            return self._get_folder_parts()
273        else:
274            raise ValueError("Unable to provide parts of the document")
275
276    def get_part(self, path: str) -> str | bytes | None:
277        """Get the bytes of a part of the ODF."""
278        path = str(path)
279        if path in self.__parts:
280            part = self.__parts[path]
281            if part is None:
282                raise ValueError(f'Part "{path}" is deleted')
283            if self.__packaging == "folder":
284                cache_ts = self.__parts_ts.get(path, -1)
285                current_ts = self._get_folder_part_timestamp(path)
286                if current_ts != cache_ts:
287                    part, timestamp = self._get_folder_part(path)
288                    self.__parts[path] = part
289                    self.__parts_ts[path] = timestamp
290            return part
291        if self.__packaging == "zip":
292            return self._get_zip_part(path)
293        if self.__packaging == "folder":
294            part, timestamp = self._get_folder_part(path)
295            self.__parts[path] = part
296            self.__parts_ts[path] = timestamp
297            return part
298        return None
299
300    @property
301    def mimetype(self) -> str:
302        """Return str value of mimetype of the document."""
303        with contextlib.suppress(Exception):
304            b_mimetype = self.get_part("mimetype")
305            if isinstance(b_mimetype, bytes):
306                return bytes_to_str(b_mimetype)
307        return ""
308
309    @mimetype.setter
310    def mimetype(self, mimetype: str | bytes) -> None:
311        """Set mimetype value of the document."""
312        if isinstance(mimetype, str):
313            self.__parts["mimetype"] = str_to_bytes(mimetype)
314        elif isinstance(mimetype, bytes):
315            self.__parts["mimetype"] = mimetype
316        else:
317            raise TypeError(f'Wrong mimetype "{mimetype!r}"')
318
319    def set_part(self, path: str, data: bytes) -> None:
320        """Replace or add a new part."""
321        self.__parts[path] = data
322
323    def del_part(self, path: str) -> None:
324        """Mark a part for deletion."""
325        self.__parts[path] = None
326
327    @property
328    def clone(self) -> Container:
329        """Make a copy of this container with no path."""
330        if self.path and self.__packaging == "zip":
331            self._get_all_zip_part()
332        clone = deepcopy(self)
333        clone.path = None
334        return clone
335
336    @staticmethod
337    def _do_backup(target: str | Path) -> None:
338        path = Path(target)
339        if not path.exists():
340            return
341        back_file = Path(path.stem + ".backup" + path.suffix)
342        if back_file.is_dir():
343            try:
344                shutil.rmtree(back_file)
345            except OSError as e:
346                printwarn(str(e))
347        try:
348            shutil.move(target, back_file)
349        except OSError as e:
350            printwarn(str(e))
351
352    def _save_packaging(self, packaging: str | None) -> str:
353        if not packaging:
354            packaging = self.__packaging if self.__packaging else "zip"
355        packaging = packaging.strip().lower()
356        # if packaging not in ('zip', 'flat', 'folder'):
357        if packaging not in ("zip", "folder"):
358            raise ValueError(f'Packaging of type "{packaging}" is not supported')
359        return packaging
360
361    def _save_target(self, target: str | Path | io.BytesIO | None) -> str | io.BytesIO:
362        if target is None:
363            target = self.path
364        if isinstance(target, Path):
365            target = str(target)
366        if isinstance(target, str):
367            while target.endswith(os.sep):
368                target = target[:-1]
369            while target.endswith(".folder"):
370                target = target.split(".folder", 1)[0]
371        return target  # type: ignore
372
373    def _save_as_zip(self, target: str | Path | io.BytesIO, backup: bool) -> None:
374        if isinstance(target, (str, Path)) and backup:
375            self._do_backup(target)
376        self._save_zip(target)
377
378    def _save_as_folder(self, target: str | Path, backup: bool) -> None:
379        if not isinstance(target, (str, Path)):
380            raise TypeError(
381                f"Saving in folder format requires a folder name, not '{target!r}'"
382            )
383        if not str(target).endswith(".folder"):
384            target = str(target) + ".folder"
385        if backup:
386            self._do_backup(target)
387        else:
388            path = Path(target)
389            if path.exists():
390                try:
391                    shutil.rmtree(path)
392                except OSError as e:
393                    printwarn(str(e))
394        self._save_folder(target)
395
396    def save(
397        self,
398        target: str | Path | io.BytesIO | None,
399        packaging: str | None = None,
400        backup: bool = False,
401    ) -> None:
402        """Save the container to the given target, a path or a file-like
403        object.
404
405        Package the output document in the same format than current document,
406        unless "packaging" is different.
407
408        Arguments:
409
410            target -- str or file-like or Path
411
412            packaging -- 'zip', or for debugging purpose 'folder'
413
414            backup -- boolean
415        """
416        parts = self.__parts
417        packaging = self._save_packaging(packaging)
418        # Load parts else they will be considered deleted
419        for path in self.get_parts():
420            if path not in parts:
421                self.get_part(path)
422        target = self._save_target(target)
423        if packaging == "folder":
424            if isinstance(target, io.BytesIO):
425                raise TypeError(
426                    "Impossible to save on io.BytesIO with 'folder' packaging"
427                )
428            self._save_as_folder(target, backup)
429        else:
430            # default:
431            self._save_as_zip(target, backup)

Representation of the ODF file.

Container(path: pathlib.Path | str | _io.BytesIO | None = None)
58    def __init__(self, path: Path | str | io.BytesIO | None = None) -> None:
59        self.__parts: dict[str, bytes | None] = {}
60        self.__parts_ts: dict[str, int] = {}
61        self.__path_like: Path | str | io.BytesIO | None = None
62        self.__packaging: str = "zip"
63        self.path: Path | None = None  # or Path
64        if path:
65            self.open(path)
path: pathlib.Path | None
def open(self, path_or_file: pathlib.Path | str | _io.BytesIO) -> None:
70    def open(self, path_or_file: Path | str | io.BytesIO) -> None:
71        """Load the content of an ODF file."""
72        self.__path_like = path_or_file
73        if isinstance(path_or_file, (str, Path)):
74            self.path = Path(path_or_file)
75            if not self.path.exists():
76                raise FileNotFoundError(str(self.path))
77        if (self.path or isinstance(path_or_file, io.BytesIO)) and is_zipfile(
78            path_or_file
79        ):
80            self.__packaging = "zip"
81            return self._read_zip()
82        if self.path:
83            is_folder = False
84            with contextlib.suppress(OSError):
85                is_folder = self.path.is_dir()
86            if is_folder:
87                self.__packaging = "folder"
88                return self._read_folder()
89        raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")

Load the content of an ODF file.

def get_parts(self) -> list[str]:
259    def get_parts(self) -> list[str]:
260        """Get the list of members."""
261        if not self.path:
262            # maybe a file like zip archive
263            return list(self.__parts.keys())
264        if self.__packaging == "zip":
265            parts = []
266            with ZipFile(self.path) as zf:
267                for name in zf.namelist():
268                    upath = normalize_path(name)
269                    parts.append(upath)
270            return parts
271        elif self.__packaging == "folder":
272            return self._get_folder_parts()
273        else:
274            raise ValueError("Unable to provide parts of the document")

Get the list of members.

def get_part(self, path: str) -> str | bytes | None:
276    def get_part(self, path: str) -> str | bytes | None:
277        """Get the bytes of a part of the ODF."""
278        path = str(path)
279        if path in self.__parts:
280            part = self.__parts[path]
281            if part is None:
282                raise ValueError(f'Part "{path}" is deleted')
283            if self.__packaging == "folder":
284                cache_ts = self.__parts_ts.get(path, -1)
285                current_ts = self._get_folder_part_timestamp(path)
286                if current_ts != cache_ts:
287                    part, timestamp = self._get_folder_part(path)
288                    self.__parts[path] = part
289                    self.__parts_ts[path] = timestamp
290            return part
291        if self.__packaging == "zip":
292            return self._get_zip_part(path)
293        if self.__packaging == "folder":
294            part, timestamp = self._get_folder_part(path)
295            self.__parts[path] = part
296            self.__parts_ts[path] = timestamp
297            return part
298        return None

Get the bytes of a part of the ODF.

mimetype: str
300    @property
301    def mimetype(self) -> str:
302        """Return str value of mimetype of the document."""
303        with contextlib.suppress(Exception):
304            b_mimetype = self.get_part("mimetype")
305            if isinstance(b_mimetype, bytes):
306                return bytes_to_str(b_mimetype)
307        return ""

Return str value of mimetype of the document.

def set_part(self, path: str, data: bytes) -> None:
319    def set_part(self, path: str, data: bytes) -> None:
320        """Replace or add a new part."""
321        self.__parts[path] = data

Replace or add a new part.

def del_part(self, path: str) -> None:
323    def del_part(self, path: str) -> None:
324        """Mark a part for deletion."""
325        self.__parts[path] = None

Mark a part for deletion.

clone: Container
327    @property
328    def clone(self) -> Container:
329        """Make a copy of this container with no path."""
330        if self.path and self.__packaging == "zip":
331            self._get_all_zip_part()
332        clone = deepcopy(self)
333        clone.path = None
334        return clone

Make a copy of this container with no path.

def save( self, target: str | pathlib.Path | _io.BytesIO | None, packaging: str | None = None, backup: bool = False) -> None:
396    def save(
397        self,
398        target: str | Path | io.BytesIO | None,
399        packaging: str | None = None,
400        backup: bool = False,
401    ) -> None:
402        """Save the container to the given target, a path or a file-like
403        object.
404
405        Package the output document in the same format than current document,
406        unless "packaging" is different.
407
408        Arguments:
409
410            target -- str or file-like or Path
411
412            packaging -- 'zip', or for debugging purpose 'folder'
413
414            backup -- boolean
415        """
416        parts = self.__parts
417        packaging = self._save_packaging(packaging)
418        # Load parts else they will be considered deleted
419        for path in self.get_parts():
420            if path not in parts:
421                self.get_part(path)
422        target = self._save_target(target)
423        if packaging == "folder":
424            if isinstance(target, io.BytesIO):
425                raise TypeError(
426                    "Impossible to save on io.BytesIO with 'folder' packaging"
427                )
428            self._save_as_folder(target, backup)
429        else:
430            # default:
431            self._save_as_zip(target, backup)

Save the container to the given target, a path or a file-like object.

Package the output document in the same format than current document, unless "packaging" is different.

Arguments:

target -- str or file-like or Path

packaging -- 'zip', or for debugging purpose 'folder'

backup -- boolean
class Content(odfdo.XmlPart):
 34class Content(XmlPart):
 35    @property
 36    def body(self) -> Element:
 37        body = self.root.document_body
 38        if not isinstance(body, Element):
 39            raise ValueError("No body found in document")  # noqa:TRY004
 40        return body
 41
 42    # The following two seem useless but they match styles API
 43
 44    def _get_style_contexts(self, family: str | None) -> tuple:
 45        if family == "font-face":
 46            return (self.get_element("//office:font-face-decls"),)
 47        return (
 48            self.get_element("//office:font-face-decls"),
 49            self.get_element("//office:automatic-styles"),
 50        )
 51
 52    def __str__(self) -> str:
 53        return str(self.body)
 54
 55    # Public API
 56
 57    def get_styles(self, family: str | None = None) -> list[Style]:
 58        """Return the list of styles in the Content part, optionally limited
 59        to the given family.
 60
 61        Arguments:
 62
 63            family -- str or None
 64
 65        Return: list of Style
 66        """
 67        result: list[Style] = []
 68        for context in self._get_style_contexts(family):
 69            if context is None:
 70                continue
 71            result.extend(context.get_styles(family=family))
 72        return result
 73
 74    def get_style(
 75        self,
 76        family: str,
 77        name_or_element: str | Element | None = None,
 78        display_name: str | None = None,
 79    ) -> Style | None:
 80        """Return the style uniquely identified by the name/family pair. If
 81        the argument is already a style object, it will return it.
 82
 83        If the name is None, the default style is fetched.
 84
 85        If the name is not the internal name but the name you gave in the
 86        desktop application, use display_name instead.
 87
 88        Arguments:
 89
 90            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
 91                      'number'
 92
 93            name_or_element -- str or Style
 94
 95            display_name -- str
 96
 97        Return: Style or None if not found
 98        """
 99        for context in self._get_style_contexts(family):
100            if context is None:
101                continue
102            style = context.get_style(
103                family,
104                name_or_element=name_or_element,
105                display_name=display_name,
106            )
107            if style is not None:
108                return style
109        return None

Representation of an XML part.

Abstraction of the XML library behind.

body: Element
35    @property
36    def body(self) -> Element:
37        body = self.root.document_body
38        if not isinstance(body, Element):
39            raise ValueError("No body found in document")  # noqa:TRY004
40        return body
def get_styles(self, family: str | None = None) -> list[Style]:
57    def get_styles(self, family: str | None = None) -> list[Style]:
58        """Return the list of styles in the Content part, optionally limited
59        to the given family.
60
61        Arguments:
62
63            family -- str or None
64
65        Return: list of Style
66        """
67        result: list[Style] = []
68        for context in self._get_style_contexts(family):
69            if context is None:
70                continue
71            result.extend(context.get_styles(family=family))
72        return result

Return the list of styles in the Content part, optionally limited to the given family.

Arguments:

family -- str or None

Return: list of Style

def get_style( self, family: str, name_or_element: str | Element | None = None, display_name: str | None = None) -> Style | None:
 74    def get_style(
 75        self,
 76        family: str,
 77        name_or_element: str | Element | None = None,
 78        display_name: str | None = None,
 79    ) -> Style | None:
 80        """Return the style uniquely identified by the name/family pair. If
 81        the argument is already a style object, it will return it.
 82
 83        If the name is None, the default style is fetched.
 84
 85        If the name is not the internal name but the name you gave in the
 86        desktop application, use display_name instead.
 87
 88        Arguments:
 89
 90            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
 91                      'number'
 92
 93            name_or_element -- str or Style
 94
 95            display_name -- str
 96
 97        Return: Style or None if not found
 98        """
 99        for context in self._get_style_contexts(family):
100            if context is None:
101                continue
102            style = context.get_style(
103                family,
104                name_or_element=name_or_element,
105                display_name=display_name,
106            )
107            if style is not None:
108                return style
109        return None

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text', 'graphic', 'table', 'list',
          'number'

name_or_element -- str or Style

display_name -- str

Return: Style or None if not found

class Document:
 155class Document:
 156    """Abstraction of the ODF document.
 157
 158    To create a new Document, several possibilities:
 159
 160        - Document() or Document("text") -> an "empty" document of type text
 161        - Document("spreadsheet") -> an "empty" document of type spreadsheet
 162        - Document("presentation") -> an "empty" document of type presentation
 163        - Document("drawing") -> an "empty" document of type drawing
 164
 165        Meaning of “empty”: these documents are copies of the default
 166        templates documents provided with this library, which, as templates,
 167        are not really empty. It may be useful to clear the newly created
 168        document: document.body.clear(), or adjust meta informations like
 169        description or default language: document.meta.set_language('fr-FR')
 170
 171    If the argument is not a known template type, or is a Path,
 172    Document(file) will load the content of the ODF file.
 173
 174    To explicitly create a document from a custom template, use the
 175    Document.new(path) method whose argument is the path to the template file.
 176    """
 177
 178    def __init__(
 179        self,
 180        target: str | bytes | Path | Container | io.BytesIO | None = "text",
 181    ) -> None:
 182        # Cache of XML parts
 183        self.__xmlparts: dict[str, XmlPart] = {}
 184        # Cache of the body
 185        self.__body: Element | None = None
 186        self.container: Container | None = None
 187        if isinstance(target, bytes):
 188            # eager conversion
 189            target = bytes_to_str(target)
 190        if target is None:
 191            # empty document, you probably don't wnat this.
 192            self.container = Container()
 193            return
 194        if isinstance(target, Path):
 195            # let's assume we open a container on existing file
 196            self.container = Container(target)
 197            return
 198        if isinstance(target, Container):
 199            # special internal case, use an existing container
 200            self.container = target
 201            return
 202        if isinstance(target, str):
 203            if target in ODF_TEMPLATES:
 204                # assuming a new document from templates
 205                self.container = container_from_template(target)
 206                return
 207            # let's assume we open a container on existing file
 208            self.container = Container(target)
 209            return
 210        if isinstance(target, io.BytesIO):
 211            self.container = Container(target)
 212            return
 213        raise TypeError(f"Unknown Document source type: '{target!r}'")
 214
 215    def __repr__(self) -> str:
 216        return f"<{self.__class__.__name__} type={self.get_type()} path={self.path}>"
 217
 218    def __str__(self) -> str:
 219        try:
 220            return str(self.get_formatted_text())
 221        except NotImplementedError:
 222            return self.body.text_recursive
 223
 224    @classmethod
 225    def new(cls, template: str | Path | io.BytesIO = "text") -> Document:
 226        """Create a Document from a template.
 227
 228        The template argument is expected to be the path to a ODF template.
 229
 230        Arguments:
 231
 232            template -- str or Path or file-like (io.BytesIO)
 233
 234        Return : ODF document -- Document
 235        """
 236        container = container_from_template(template)
 237        return cls(container)
 238
 239    # Public API
 240
 241    @property
 242    def path(self) -> Path | None:
 243        """Shortcut to Document.Container.path."""
 244        if not self.container:
 245            return None
 246        return self.container.path
 247
 248    @path.setter
 249    def path(self, path_or_str: str | Path) -> None:
 250        """Shortcut to Document.Container.path
 251
 252        Only accepting str or Path."""
 253        if not self.container:
 254            return
 255        self.container.path = Path(path_or_str)
 256
 257    def get_parts(self) -> list[str]:
 258        """Return available part names with path inside the archive, e.g.
 259        ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
 260        """
 261        if not self.container:
 262            raise ValueError("Empty Container")
 263        return self.container.get_parts()
 264
 265    def get_part(self, path: str) -> XmlPart | str | bytes | None:
 266        """Return the bytes of the given part. The path is relative to the
 267        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
 268
 269        'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts
 270        to the real path, e.g. content.xml, and return a dedicated object with
 271        its own API.
 272
 273        path formated as URI, so always use '/' separator
 274        """
 275        if not self.container:
 276            raise ValueError("Empty Container")
 277        # "./ObjectReplacements/Object 1"
 278        path = path.lstrip("./")
 279        path = _get_part_path(path)
 280        cls = _get_part_class(path)
 281        # Raw bytes
 282        if cls is None:
 283            return self.container.get_part(path)
 284        # XML part
 285        part = self.__xmlparts.get(path)
 286        if part is None:
 287            self.__xmlparts[path] = part = cls(path, self.container)
 288        return part
 289
 290    def set_part(self, path: str, data: bytes) -> None:
 291        """Set the bytes of the given part. The path is relative to the
 292        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
 293
 294        path formated as URI, so always use '/' separator
 295        """
 296        if not self.container:
 297            raise ValueError("Empty Container")
 298        # "./ObjectReplacements/Object 1"
 299        path = path.lstrip("./")
 300        path = _get_part_path(path)
 301        cls = _get_part_class(path)
 302        # XML part overwritten
 303        if cls is not None:
 304            with suppress(KeyError):
 305                self.__xmlparts[path]
 306        self.container.set_part(path, data)
 307
 308    def del_part(self, path: str) -> None:
 309        """Mark a part for deletion. The path is relative to the archive,
 310        e.g. "Pictures/1003200258912EB1C3.jpg"
 311        """
 312        if not self.container:
 313            raise ValueError("Empty Container")
 314        path = _get_part_path(path)
 315        cls = _get_part_class(path)
 316        if path == ODF_MANIFEST or cls is not None:
 317            raise ValueError(f"part '{path}' is mandatory")
 318        self.container.del_part(path)
 319
 320    @property
 321    def mimetype(self) -> str:
 322        if not self.container:
 323            raise ValueError("Empty Container")
 324        return self.container.mimetype
 325
 326    @mimetype.setter
 327    def mimetype(self, mimetype: str) -> None:
 328        if not self.container:
 329            raise ValueError("Empty Container")
 330        self.container.mimetype = mimetype
 331
 332    def get_type(self) -> str:
 333        """Get the ODF type (also called class) of this document.
 334
 335        Return: 'chart', 'database', 'formula', 'graphics',
 336            'graphics-template', 'image', 'presentation',
 337            'presentation-template', 'spreadsheet', 'spreadsheet-template',
 338            'text', 'text-master', 'text-template' or 'text-web'
 339        """
 340        # The mimetype must be with the form:
 341        # application/vnd.oasis.opendocument.text
 342
 343        # Isolate and return the last part
 344        return self.mimetype.rsplit(".", 1)[-1]
 345
 346    @property
 347    def body(self) -> Element:
 348        """Return the body element of the content part, where actual content
 349        is stored.
 350        """
 351        if self.__body is None:
 352            self.__body = self.content.body
 353        return self.__body
 354
 355    @property
 356    def meta(self) -> Meta:
 357        """Return the meta part (meta.xml) of the document, where meta data
 358        are stored."""
 359        metadata = self.get_part(ODF_META)
 360        if metadata is None or not isinstance(metadata, Meta):
 361            raise ValueError("Empty Meta")
 362        return metadata
 363
 364    @property
 365    def manifest(self) -> Manifest:
 366        """Return the manifest part (manifest.xml) of the document."""
 367        manifest = self.get_part(ODF_MANIFEST)
 368        if manifest is None or not isinstance(manifest, Manifest):
 369            raise ValueError("Empty Manifest")
 370        return manifest
 371
 372    def _get_formatted_text_footnotes(
 373        self,
 374        result: list[str],
 375        context: dict,
 376        rst_mode: bool,
 377    ) -> None:
 378        # Separate text from notes
 379        if rst_mode:
 380            result.append("\n")
 381        else:
 382            result.append("----\n")
 383        for citation, body in context["footnotes"]:
 384            if rst_mode:
 385                result.append(f".. [#] {body}\n")
 386            else:
 387                result.append(f"[{citation}] {body}\n")
 388        # Append a \n after the notes
 389        result.append("\n")
 390        # Reset for the next paragraph
 391        context["footnotes"] = []
 392
 393    def _get_formatted_text_annotations(
 394        self,
 395        result: list[str],
 396        context: dict,
 397        rst_mode: bool,
 398    ) -> None:
 399        # Insert the annotations
 400        # With a separation
 401        if rst_mode:
 402            result.append("\n")
 403        else:
 404            result.append("----\n")
 405        for annotation in context["annotations"]:
 406            if rst_mode:
 407                result.append(f".. [#] {annotation}\n")
 408            else:
 409                result.append(f"[*] {annotation}\n")
 410        context["annotations"] = []
 411
 412    def _get_formatted_text_images(
 413        self,
 414        result: list[str],
 415        context: dict,
 416        rst_mode: bool,
 417    ) -> None:
 418        # Insert the images ref, only in rst mode
 419        result.append("\n")
 420        for ref, filename, (width, height) in context["images"]:
 421            result.append(f".. {ref} image:: {filename}\n")
 422            if width is not None:
 423                result.append(f"   :width: {width}\n")
 424            if height is not None:
 425                result.append(f"   :height: {height}\n")
 426        context["images"] = []
 427
 428    def _get_formatted_text_endnotes(
 429        self,
 430        result: list[str],
 431        context: dict,
 432        rst_mode: bool,
 433    ) -> None:
 434        # Append the end notes
 435        if rst_mode:
 436            result.append("\n\n")
 437        else:
 438            result.append("\n========\n")
 439        for citation, body in context["endnotes"]:
 440            if rst_mode:
 441                result.append(f".. [*] {body}\n")
 442            else:
 443                result.append(f"({citation}) {body}\n")
 444
 445    def get_formatted_text(self, rst_mode: bool = False) -> str:
 446        """Return content as text, with some formatting."""
 447        # For the moment, only "type='text'"
 448        doc_type = self.get_type()
 449        if doc_type == "spreadsheet":
 450            return self._tables_csv()
 451        if doc_type in {
 452            "text",
 453            "text-template",
 454            "presentation",
 455            "presentation-template",
 456        }:
 457            return self._formatted_text(rst_mode)
 458        raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")
 459
 460    def _tables_csv(self) -> str:
 461        return "\n\n".join(str(table) for table in self.body.get_tables())
 462
 463    def _formatted_text(self, rst_mode: bool) -> str:
 464        # Initialize an empty context
 465        context = {
 466            "document": self,
 467            "footnotes": [],
 468            "endnotes": [],
 469            "annotations": [],
 470            "rst_mode": rst_mode,
 471            "img_counter": 0,
 472            "images": [],
 473            "no_img_level": 0,
 474        }
 475        body = self.body
 476        # Get the text
 477        result = []
 478        for child in body.children:
 479            # self._get_formatted_text_child(result, element, context, rst_mode)
 480            # if child.tag == "table:table":
 481            #     result.append(child.get_formatted_text(context))
 482            #     return
 483            result.append(child.get_formatted_text(context))
 484            if context["footnotes"]:
 485                self._get_formatted_text_footnotes(result, context, rst_mode)
 486            if context["annotations"]:
 487                self._get_formatted_text_annotations(result, context, rst_mode)
 488            # Insert the images ref, only in rst mode
 489            if context["images"]:
 490                self._get_formatted_text_images(result, context, rst_mode)
 491        if context["endnotes"]:
 492            self._get_formatted_text_endnotes(result, context, rst_mode)
 493        return "".join(result)
 494
 495    def get_formated_meta(self) -> str:
 496        """Return meta informations as text, with some formatting."""
 497        result: list[str] = []
 498
 499        # Simple values
 500        def print_info(name: str, value: Any) -> None:
 501            if value:
 502                result.append(f"{name}: {value}")
 503
 504        meta = self.meta
 505        print_info("Title", meta.get_title())
 506        print_info("Subject", meta.get_subject())
 507        print_info("Language", meta.get_language())
 508        print_info("Modification date", meta.get_modification_date())
 509        print_info("Creation date", meta.get_creation_date())
 510        print_info("Initial creator", meta.get_initial_creator())
 511        print_info("Keyword", meta.get_keywords())
 512        print_info("Editing duration", meta.get_editing_duration())
 513        print_info("Editing cycles", meta.get_editing_cycles())
 514        print_info("Generator", meta.get_generator())
 515
 516        # Statistic
 517        result.append("Statistic:")
 518        statistic = meta.get_statistic()
 519        if statistic:
 520            for name, data in statistic.items():
 521                result.append(f"  - {name[5:].replace('-', ' ').capitalize()}: {data}")
 522
 523        # User defined metadata
 524        result.append("User defined metadata:")
 525        user_metadata = meta.get_user_defined_metadata()
 526        for name, data2 in user_metadata.items():
 527            result.append(f"  - {name}: {data2}")
 528
 529        # And the description
 530        print_info("Description", meta.get_description())
 531
 532        return "\n".join(result) + "\n"
 533
 534    def add_file(self, path_or_file: str | Path) -> str:
 535        """Insert a file from a path or a file-like object in the container.
 536
 537        Return the full path to reference in the content.
 538
 539        Arguments:
 540
 541            path_or_file -- str or Path or file-like
 542
 543        Return: str (URI)
 544        """
 545        if not self.container:
 546            raise ValueError("Empty Container")
 547        name = ""
 548        # Folder for added files (FIXME hard-coded and copied)
 549        manifest = self.manifest
 550        medias = manifest.get_paths()
 551        # uuid = str(uuid4())
 552
 553        if isinstance(path_or_file, (str, Path)):
 554            path = Path(path_or_file)
 555            extension = path.suffix.lower()
 556            name = f"{path.stem}{extension}"
 557            if posixpath.join("Pictures", name) in medias:
 558                name = f"{path.stem}_{uuid4()}{extension}"
 559        else:
 560            path = None
 561            name = getattr(path_or_file, "name", None)
 562            if not name:
 563                name = str(uuid4())
 564        media_type, _encoding = guess_type(name)
 565        if not media_type:
 566            media_type = "application/octet-stream"
 567        if manifest.get_media_type("Pictures/") is None:
 568            manifest.add_full_path("Pictures/")
 569        full_path = posixpath.join("Pictures", name)
 570        if path is None:
 571            self.container.set_part(full_path, path_or_file.read())
 572        else:
 573            self.container.set_part(full_path, path.read_bytes())
 574        manifest.add_full_path(full_path, media_type)
 575        return full_path
 576
 577    @property
 578    def clone(self) -> Document:
 579        """Return an exact copy of the document.
 580
 581        Return: Document
 582        """
 583        clone = object.__new__(self.__class__)
 584        for name in self.__dict__:
 585            if name == "_Document__body":
 586                setattr(clone, name, None)
 587            elif name == "_Document__xmlparts":
 588                setattr(clone, name, {})
 589            elif name == "container":
 590                if not self.container:
 591                    raise ValueError("Empty Container")
 592                setattr(clone, name, self.container.clone)
 593            else:
 594                value = deepcopy(getattr(self, name))
 595                setattr(clone, name, value)
 596        return clone
 597
 598    def save(
 599        self,
 600        target: str | Path | io.BytesIO | None = None,
 601        packaging: str = "zip",
 602        pretty: bool = False,
 603        backup: bool = False,
 604    ) -> None:
 605        """Save the document, at the same place it was opened or at the given
 606        target path. Target can also be a file-like object. It can be saved
 607        as a Zip file (default) or as files in a folder (for debugging
 608        purpose). XML parts can be pretty printed.
 609
 610        Arguments:
 611
 612            target -- str or file-like object
 613
 614            packaging -- 'zip' or 'folder'
 615
 616            pretty -- bool
 617
 618            backup -- bool
 619        """
 620        if not self.container:
 621            raise ValueError("Empty Container")
 622        # Some advertising
 623        self.meta.set_generator_default()
 624        # Synchronize data with container
 625        container = self.container
 626        for path, part in self.__xmlparts.items():
 627            if part is not None:
 628                container.set_part(path, part.serialize(pretty))
 629        # Save the container
 630        container.save(target, packaging=packaging, backup=backup)
 631
 632    @property
 633    def content(self) -> Content:
 634        content: Content | None = self.get_part(ODF_CONTENT)  # type:ignore
 635        if content is None:
 636            raise ValueError("Empty Content")
 637        return content
 638
 639    @property
 640    def styles(self) -> Styles:
 641        styles: Styles | None = self.get_part(ODF_STYLES)  # type:ignore
 642        if styles is None:
 643            raise ValueError("Empty Styles")
 644        return styles
 645
 646    # Styles over several parts
 647
 648    def get_styles(
 649        self,
 650        family: str | bytes = "",
 651        automatic: bool = False,
 652    ) -> list[Style | Element]:
 653        # compatibility with old versions:
 654
 655        if isinstance(family, bytes):
 656            family = bytes_to_str(family)
 657        return self.content.get_styles(family=family) + self.styles.get_styles(
 658            family=family, automatic=automatic
 659        )
 660
 661    def get_style(
 662        self,
 663        family: str,
 664        name_or_element: str | Style | None = None,
 665        display_name: str | None = None,
 666    ) -> Style | None:
 667        """Return the style uniquely identified by the name/family pair. If
 668        the argument is already a style object, it will return it.
 669
 670        If the name is None, the default style is fetched.
 671
 672        If the name is not the internal name but the name you gave in a
 673        desktop application, use display_name instead.
 674
 675        Arguments:
 676
 677            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
 678                      'number', 'page-layout', 'master-page'
 679
 680            name -- str or Element or None
 681
 682            display_name -- str
 683
 684        Return: Style or None if not found.
 685        """
 686        # 1. content.xml
 687        element = self.content.get_style(
 688            family, name_or_element=name_or_element, display_name=display_name
 689        )
 690        if element is not None:
 691            return element
 692        # 2. styles.xml
 693        return self.styles.get_style(
 694            family,
 695            name_or_element=name_or_element,
 696            display_name=display_name,
 697        )
 698
 699    @staticmethod
 700    def _pseudo_style_attribute(style_element: Style | Element, attribute: str) -> Any:
 701        if hasattr(style_element, attribute):
 702            return getattr(style_element, attribute)
 703        return ""
 704
 705    def _set_automatic_name(self, style: Style, family: str) -> None:
 706        """Generate a name for the new automatic style."""
 707        if not hasattr(style, "name"):
 708            # do nothing
 709            return
 710        styles = self.get_styles(family=family, automatic=True)
 711        max_index = 0
 712        for existing_style in styles:
 713            if not hasattr(existing_style, "name"):
 714                continue
 715            if not existing_style.name.startswith(AUTOMATIC_PREFIX):
 716                continue
 717            try:
 718                index = int(existing_style.name[len(AUTOMATIC_PREFIX) :])
 719            except ValueError:
 720                continue
 721            max_index = max(max_index, index)
 722
 723        style.name = f"{AUTOMATIC_PREFIX}{max_index+1}"
 724
 725    def _insert_style_get_common_styles(
 726        self,
 727        family: str,
 728        name: str,
 729    ) -> tuple[Any, Any]:
 730        style_container = self.styles.get_element("office:styles")
 731        existing = self.styles.get_style(family, name)
 732        return existing, style_container
 733
 734    def _insert_style_get_automatic_styles(
 735        self,
 736        style: Style,
 737        family: str,
 738        name: str,
 739    ) -> tuple[Any, Any]:
 740        style_container = self.content.get_element("office:automatic-styles")
 741        # A name ?
 742        if name:
 743            if hasattr(style, "name"):
 744                style.name = name
 745            existing = self.content.get_style(family, name)
 746        else:
 747            self._set_automatic_name(style, family)
 748            existing = None
 749        return existing, style_container
 750
 751    def _insert_style_get_default_styles(
 752        self,
 753        style: Style,
 754        family: str,
 755        name: str,
 756    ) -> tuple[Any, Any]:
 757        style_container = self.styles.get_element("office:styles")
 758        style.tag = "style:default-style"
 759        if name:
 760            style.del_attribute("style:name")
 761        existing = self.styles.get_style(family)
 762        return existing, style_container
 763
 764    def _insert_style_get_master_page(
 765        self,
 766        family: str,
 767        name: str,
 768    ) -> tuple[Any, Any]:
 769        style_container = self.styles.get_element("office:master-styles")
 770        existing = self.styles.get_style(family, name)
 771        return existing, style_container
 772
 773    def _insert_style_get_font_face_default(
 774        self,
 775        family: str,
 776        name: str,
 777    ) -> tuple[Any, Any]:
 778        style_container = self.styles.get_element("office:font-face-decls")
 779        existing = self.styles.get_style(family, name)
 780        return existing, style_container
 781
 782    def _insert_style_get_font_face(
 783        self,
 784        family: str,
 785        name: str,
 786    ) -> tuple[Any, Any]:
 787        style_container = self.content.get_element("office:font-face-decls")
 788        existing = self.content.get_style(family, name)
 789        return existing, style_container
 790
 791    def _insert_style_get_page_layout(
 792        self,
 793        family: str,
 794        name: str,
 795    ) -> tuple[Any, Any]:
 796        # force to automatic
 797        style_container = self.styles.get_element("office:automatic-styles")
 798        existing = self.styles.get_style(family, name)
 799        return existing, style_container
 800
 801    def _insert_style_get_draw_fill_image(
 802        self,
 803        name: str,
 804    ) -> tuple[Any, Any]:
 805        # special case for 'draw:fill-image' pseudo style
 806        # not family and style_element.__class__.__name__ == "DrawFillImage"
 807        style_container = self.styles.get_element("office:styles")
 808        existing = self.styles.get_style("", name)
 809        return existing, style_container
 810
 811    def _insert_style_standard(
 812        self,
 813        style: Style,
 814        name: str,
 815        family: str,
 816        automatic: bool,
 817        default: bool,
 818    ) -> tuple[Any, Any]:
 819        # Common style
 820        if name and automatic is False and default is False:
 821            return self._insert_style_get_common_styles(family, name)
 822        # Automatic style
 823        elif automatic is True and default is False:
 824            return self._insert_style_get_automatic_styles(style, family, name)
 825        # Default style
 826        elif automatic is False and default is True:
 827            return self._insert_style_get_default_styles(style, family, name)
 828        else:
 829            raise AttributeError("Invalid combination of arguments")
 830
 831    def insert_style(  # noqa: C901
 832        self,
 833        style: Style | str,
 834        name: str = "",
 835        automatic: bool = False,
 836        default: bool = False,
 837    ) -> Any:
 838        """Insert the given style object in the document, as required by the
 839        style family and type.
 840
 841        The style is expected to be a common style with a name. In case it
 842        was created with no name, the given can be set on the fly.
 843
 844        If automatic is True, the style will be inserted as an automatic
 845        style.
 846
 847        If default is True, the style will be inserted as a default style and
 848        would replace any existing default style of the same family. Any name
 849        or display name would be ignored.
 850
 851        Automatic and default arguments are mutually exclusive.
 852
 853        All styles can't be used as default styles. Default styles are
 854        allowed for the following families: paragraph, text, section, table,
 855        table-column, table-row, table-cell, table-page, chart, drawing-page,
 856        graphic, presentation, control and ruby.
 857
 858        Arguments:
 859
 860            style -- Style or str
 861
 862            name -- str
 863
 864            automatic -- bool
 865
 866            default -- bool
 867
 868        Return : style name -- str
 869        """
 870
 871        # if style is a str, assume it is the Style definition
 872        if isinstance(style, str):
 873            style_element: Style = Element.from_tag(style)  # type: ignore
 874        else:
 875            style_element = style
 876        if not isinstance(style_element, Element):
 877            raise TypeError(f"Unknown Style type: '{style!r}'")
 878
 879        # Get family and name
 880        family = self._pseudo_style_attribute(style_element, "family")
 881        if not name:
 882            name = self._pseudo_style_attribute(style_element, "name")
 883
 884        # Master page style
 885        if family == "master-page":
 886            existing, style_container = self._insert_style_get_master_page(family, name)
 887        # Font face declarations
 888        elif family == "font-face":
 889            if default:
 890                existing, style_container = self._insert_style_get_font_face_default(
 891                    family, name
 892                )
 893            else:
 894                existing, style_container = self._insert_style_get_font_face(
 895                    family, name
 896                )
 897        # page layout style
 898        elif family == "page-layout":
 899            existing, style_container = self._insert_style_get_page_layout(family, name)
 900        # Common style
 901        elif family in FAMILY_ODF_STD or family in {"number"}:
 902            existing, style_container = self._insert_style_standard(
 903                style_element, name, family, automatic, default
 904            )
 905        elif not family and style_element.__class__.__name__ == "DrawFillImage":
 906            # special case for 'draw:fill-image' pseudo style
 907            existing, style_container = self._insert_style_get_draw_fill_image(name)
 908        # Invalid style
 909        else:
 910            raise ValueError(
 911                "Invalid style: "
 912                f"{style_element}, tag:{style_element.tag}, family:{family}"
 913            )
 914
 915        # Insert it!
 916        if existing is not None:
 917            style_container.delete(existing)
 918        style_container.append(style_element)
 919        return self._pseudo_style_attribute(style_element, "name")
 920
 921    def get_styled_elements(self, name: str = "") -> list[Element]:
 922        """Brute-force to find paragraphs, tables, etc. using the given style
 923        name (or all by default).
 924
 925        Arguments:
 926
 927            name -- str
 928
 929        Return: list
 930        """
 931        # Header, footer, etc. have styles too
 932        return self.content.root.get_styled_elements(
 933            name
 934        ) + self.styles.root.get_styled_elements(name)
 935
 936    def show_styles(
 937        self,
 938        automatic: bool = True,
 939        common: bool = True,
 940        properties: bool = False,
 941    ) -> str:
 942        infos = []
 943        for style in self.get_styles():
 944            try:
 945                name = style.name  # type: ignore
 946            except AttributeError:
 947                print("--------------")
 948                print(style.__class__)
 949                print(style.serialize())
 950                raise
 951            if style.__class__.__name__ == "DrawFillImage":
 952                family = ""
 953            else:
 954                family = str(style.family)  # type: ignore
 955            parent = style.parent
 956            is_auto = parent and parent.tag == "office:automatic-styles"
 957            if is_auto and automatic is False or not is_auto and common is False:
 958                continue
 959            is_used = bool(self.get_styled_elements(name))
 960            infos.append(
 961                {
 962                    "type": "auto  " if is_auto else "common",
 963                    "used": "y" if is_used else "n",
 964                    "family": family,
 965                    "parent": self._pseudo_style_attribute(style, "parent_style") or "",
 966                    "name": name or "",
 967                    "display_name": self._pseudo_style_attribute(style, "display_name")
 968                    or "",
 969                    "properties": style.get_properties() if properties else None,  # type: ignore
 970                }
 971            )
 972        if not infos:
 973            return ""
 974        # Sort by family and name
 975        infos.sort(key=itemgetter("family", "name"))
 976        # Show common and used first
 977        infos.sort(key=itemgetter("type", "used"), reverse=True)
 978        max_family = str(max([len(x["family"]) for x in infos]))  # type: ignore
 979        max_parent = str(max([len(x["parent"]) for x in infos]))  # type: ignore
 980        formater = (
 981            "%(type)s used:%(used)s family:%(family)-0"
 982            + max_family
 983            + "s parent:%(parent)-0"
 984            + max_parent
 985            + "s name:%(name)s"
 986        )
 987        output = []
 988        for info in infos:
 989            line = formater % info
 990            if info["display_name"]:
 991                line += " display_name:" + info["display_name"]  # type: ignore
 992            output.append(line)
 993            if info["properties"]:
 994                for name, value in info["properties"].items():  # type: ignore
 995                    output.append(f"   - {name}: {value}")
 996        output.append("")
 997        return "\n".join(output)
 998
 999    def delete_styles(self) -> int:
1000        """Remove all style information from content and all styles.
1001
1002        Return: number of deleted styles
1003        """
1004        # First remove references to styles
1005        for element in self.get_styled_elements():
1006            for attribute in (
1007                "text:style-name",
1008                "draw:style-name",
1009                "draw:text-style-name",
1010                "table:style-name",
1011                "style:page-layout-name",
1012            ):
1013                try:
1014                    element.del_attribute(attribute)
1015                except KeyError:
1016                    continue
1017        # Then remove supposedly orphaned styles
1018        deleted = 0
1019        for style in self.get_styles():
1020            if style.name is None:  # type: ignore
1021                # Don't delete default styles
1022                continue
1023            # elif type(style) is odf_master_page:
1024            #    # Don't suppress header and footer, just styling was removed
1025            #    continue
1026            style.delete()
1027            deleted += 1
1028        return deleted
1029
1030    def merge_styles_from(self, document: Document) -> None:
1031        """Copy all the styles of a document into ourself.
1032
1033        Styles with the same type and name will be replaced, so only unique
1034        styles will be preserved.
1035        """
1036        manifest = self.manifest
1037        document_manifest = document.manifest
1038        for style in document.get_styles():
1039            tagname = style.tag
1040            family = self._pseudo_style_attribute(style, "family")
1041            stylename = style.name  # type: ignore
1042            container = style.parent
1043            container_name = container.tag  # type: ignore
1044            partname = container.parent.tag  # type: ignore
1045            # The destination part
1046            if partname == "office:document-styles":
1047                part: Content | Styles = self.styles
1048            elif partname == "office:document-content":
1049                part = self.content
1050            else:
1051                raise NotImplementedError(partname)
1052            # Implemented containers
1053            if container_name not in {
1054                "office:styles",
1055                "office:automatic-styles",
1056                "office:master-styles",
1057                "office:font-face-decls",
1058            }:
1059                raise NotImplementedError(container_name)
1060            dest = part.get_element(f"//{container_name}")
1061            # Implemented style types
1062            # if tagname not in registered_styles:
1063            #    raise NotImplementedError(tagname)
1064            duplicate = part.get_style(family, stylename)
1065            if duplicate is not None:
1066                duplicate.delete()
1067            dest.append(style)
1068            # Copy images from the header/footer
1069            if tagname == "style:master-page":
1070                query = "descendant::draw:image"
1071                for image in style.get_elements(query):
1072                    url = image.url  # type: ignore
1073                    part_url = document.get_part(url)
1074                    # Manually add the part to keep the name
1075                    self.set_part(url, part_url)  # type: ignore
1076                    media_type = document_manifest.get_media_type(url)
1077                    manifest.add_full_path(url, media_type)  # type: ignore
1078            # Copy images from the fill-image
1079            elif tagname == "draw:fill-image":
1080                url = style.url  # type: ignore
1081                part_url = document.get_part(url)
1082                self.set_part(url, part_url)  # type: ignore
1083                media_type = document_manifest.get_media_type(url)
1084                manifest.add_full_path(url, media_type)  # type: ignore
1085
1086    def add_page_break_style(self) -> None:
1087        """Ensure that the document contains the style required for a manual page break.
1088
1089        Then a manual page break can be added to the document with:
1090            from paragraph import PageBreak
1091            ...
1092            document.body.append(PageBreak())
1093
1094        Note: this style uses the property 'fo:break-after', another
1095        possibility could be the property 'fo:break-before'
1096        """
1097        if existing := self.get_style(  # noqa: SIM102
1098            family="paragraph",
1099            name_or_element="odfdopagebreak",
1100        ):
1101            if properties := existing.get_properties():  # noqa: SIM102
1102                if properties["fo:break-after"] == "page":
1103                    return
1104        style = (
1105            '<style:style style:family="paragraph" style:parent-style-name="Standard" '
1106            'style:name="odfdopagebreak">'
1107            '<style:paragraph-properties fo:break-after="page"/></style:style>'
1108        )
1109        self.insert_style(style, automatic=False)

Abstraction of the ODF document.

To create a new Document, several possibilities:

- Document() or Document("text") -> an "empty" document of type text
- Document("spreadsheet") -> an "empty" document of type spreadsheet
- Document("presentation") -> an "empty" document of type presentation
- Document("drawing") -> an "empty" document of type drawing

Meaning of “empty”: these documents are copies of the default
templates documents provided with this library, which, as templates,
are not really empty. It may be useful to clear the newly created
document: document.body.clear(), or adjust meta informations like
description or default language: document.meta.set_language('fr-FR')

If the argument is not a known template type, or is a Path, Document(file) will load the content of the ODF file.

To explicitly create a document from a custom template, use the Document.new(path) method whose argument is the path to the template file.

Document( target: str | bytes | pathlib.Path | Container | _io.BytesIO | None = 'text')
178    def __init__(
179        self,
180        target: str | bytes | Path | Container | io.BytesIO | None = "text",
181    ) -> None:
182        # Cache of XML parts
183        self.__xmlparts: dict[str, XmlPart] = {}
184        # Cache of the body
185        self.__body: Element | None = None
186        self.container: Container | None = None
187        if isinstance(target, bytes):
188            # eager conversion
189            target = bytes_to_str(target)
190        if target is None:
191            # empty document, you probably don't wnat this.
192            self.container = Container()
193            return
194        if isinstance(target, Path):
195            # let's assume we open a container on existing file
196            self.container = Container(target)
197            return
198        if isinstance(target, Container):
199            # special internal case, use an existing container
200            self.container = target
201            return
202        if isinstance(target, str):
203            if target in ODF_TEMPLATES:
204                # assuming a new document from templates
205                self.container = container_from_template(target)
206                return
207            # let's assume we open a container on existing file
208            self.container = Container(target)
209            return
210        if isinstance(target, io.BytesIO):
211            self.container = Container(target)
212            return
213        raise TypeError(f"Unknown Document source type: '{target!r}'")
container: Container | None
@classmethod
def new( cls, template: str | pathlib.Path | _io.BytesIO = 'text') -> Document:
224    @classmethod
225    def new(cls, template: str | Path | io.BytesIO = "text") -> Document:
226        """Create a Document from a template.
227
228        The template argument is expected to be the path to a ODF template.
229
230        Arguments:
231
232            template -- str or Path or file-like (io.BytesIO)
233
234        Return : ODF document -- Document
235        """
236        container = container_from_template(template)
237        return cls(container)

Create a Document from a template.

The template argument is expected to be the path to a ODF template.

Arguments:

template -- str or Path or file-like (io.BytesIO)

Return : ODF document -- Document

path: pathlib.Path | None
241    @property
242    def path(self) -> Path | None:
243        """Shortcut to Document.Container.path."""
244        if not self.container:
245            return None
246        return self.container.path

Shortcut to Document.Container.path.

def get_parts(self) -> list[str]:
257    def get_parts(self) -> list[str]:
258        """Return available part names with path inside the archive, e.g.
259        ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
260        """
261        if not self.container:
262            raise ValueError("Empty Container")
263        return self.container.get_parts()

Return available part names with path inside the archive, e.g. ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']

def get_part(self, path: str) -> XmlPart | str | bytes | None:
265    def get_part(self, path: str) -> XmlPart | str | bytes | None:
266        """Return the bytes of the given part. The path is relative to the
267        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
268
269        'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts
270        to the real path, e.g. content.xml, and return a dedicated object with
271        its own API.
272
273        path formated as URI, so always use '/' separator
274        """
275        if not self.container:
276            raise ValueError("Empty Container")
277        # "./ObjectReplacements/Object 1"
278        path = path.lstrip("./")
279        path = _get_part_path(path)
280        cls = _get_part_class(path)
281        # Raw bytes
282        if cls is None:
283            return self.container.get_part(path)
284        # XML part
285        part = self.__xmlparts.get(path)
286        if part is None:
287            self.__xmlparts[path] = part = cls(path, self.container)
288        return part

Return the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".

'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts to the real path, e.g. content.xml, and return a dedicated object with its own API.

path formated as URI, so always use '/' separator

def set_part(self, path: str, data: bytes) -> None:
290    def set_part(self, path: str, data: bytes) -> None:
291        """Set the bytes of the given part. The path is relative to the
292        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
293
294        path formated as URI, so always use '/' separator
295        """
296        if not self.container:
297            raise ValueError("Empty Container")
298        # "./ObjectReplacements/Object 1"
299        path = path.lstrip("./")
300        path = _get_part_path(path)
301        cls = _get_part_class(path)
302        # XML part overwritten
303        if cls is not None:
304            with suppress(KeyError):
305                self.__xmlparts[path]
306        self.container.set_part(path, data)

Set the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".

path formated as URI, so always use '/' separator

def del_part(self, path: str) -> None:
308    def del_part(self, path: str) -> None:
309        """Mark a part for deletion. The path is relative to the archive,
310        e.g. "Pictures/1003200258912EB1C3.jpg"
311        """
312        if not self.container:
313            raise ValueError("Empty Container")
314        path = _get_part_path(path)
315        cls = _get_part_class(path)
316        if path == ODF_MANIFEST or cls is not None:
317            raise ValueError(f"part '{path}' is mandatory")
318        self.container.del_part(path)

Mark a part for deletion. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg"

mimetype: str
320    @property
321    def mimetype(self) -> str:
322        if not self.container:
323            raise ValueError("Empty Container")
324        return self.container.mimetype
def get_type(self) -> str:
332    def get_type(self) -> str:
333        """Get the ODF type (also called class) of this document.
334
335        Return: 'chart', 'database', 'formula', 'graphics',
336            'graphics-template', 'image', 'presentation',
337            'presentation-template', 'spreadsheet', 'spreadsheet-template',
338            'text', 'text-master', 'text-template' or 'text-web'
339        """
340        # The mimetype must be with the form:
341        # application/vnd.oasis.opendocument.text
342
343        # Isolate and return the last part
344        return self.mimetype.rsplit(".", 1)[-1]

Get the ODF type (also called class) of this document.

Return: 'chart', 'database', 'formula', 'graphics', 'graphics-template', 'image', 'presentation', 'presentation-template', 'spreadsheet', 'spreadsheet-template', 'text', 'text-master', 'text-template' or 'text-web'

body: Element
346    @property
347    def body(self) -> Element:
348        """Return the body element of the content part, where actual content
349        is stored.
350        """
351        if self.__body is None:
352            self.__body = self.content.body
353        return self.__body

Return the body element of the content part, where actual content is stored.

meta: Meta
355    @property
356    def meta(self) -> Meta:
357        """Return the meta part (meta.xml) of the document, where meta data
358        are stored."""
359        metadata = self.get_part(ODF_META)
360        if metadata is None or not isinstance(metadata, Meta):
361            raise ValueError("Empty Meta")
362        return metadata

Return the meta part (meta.xml) of the document, where meta data are stored.

manifest: Manifest
364    @property
365    def manifest(self) -> Manifest:
366        """Return the manifest part (manifest.xml) of the document."""
367        manifest = self.get_part(ODF_MANIFEST)
368        if manifest is None or not isinstance(manifest, Manifest):
369            raise ValueError("Empty Manifest")
370        return manifest

Return the manifest part (manifest.xml) of the document.

def get_formatted_text(self, rst_mode: bool = False) -> str:
445    def get_formatted_text(self, rst_mode: bool = False) -> str:
446        """Return content as text, with some formatting."""
447        # For the moment, only "type='text'"
448        doc_type = self.get_type()
449        if doc_type == "spreadsheet":
450            return self._tables_csv()
451        if doc_type in {
452            "text",
453            "text-template",
454            "presentation",
455            "presentation-template",
456        }:
457            return self._formatted_text(rst_mode)
458        raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")

Return content as text, with some formatting.

def get_formated_meta(self) -> str:
495    def get_formated_meta(self) -> str:
496        """Return meta informations as text, with some formatting."""
497        result: list[str] = []
498
499        # Simple values
500        def print_info(name: str, value: Any) -> None:
501            if value:
502                result.append(f"{name}: {value}")
503
504        meta = self.meta
505        print_info("Title", meta.get_title())
506        print_info("Subject", meta.get_subject())
507        print_info("Language", meta.get_language())
508        print_info("Modification date", meta.get_modification_date())
509        print_info("Creation date", meta.get_creation_date())
510        print_info("Initial creator", meta.get_initial_creator())
511        print_info("Keyword", meta.get_keywords())
512        print_info("Editing duration", meta.get_editing_duration())
513        print_info("Editing cycles", meta.get_editing_cycles())
514        print_info("Generator", meta.get_generator())
515
516        # Statistic
517        result.append("Statistic:")
518        statistic = meta.get_statistic()
519        if statistic:
520            for name, data in statistic.items():
521                result.append(f"  - {name[5:].replace('-', ' ').capitalize()}: {data}")
522
523        # User defined metadata
524        result.append("User defined metadata:")
525        user_metadata = meta.get_user_defined_metadata()
526        for name, data2 in user_metadata.items():
527            result.append(f"  - {name}: {data2}")
528
529        # And the description
530        print_info("Description", meta.get_description())
531
532        return "\n".join(result) + "\n"

Return meta informations as text, with some formatting.

def add_file(self, path_or_file: str | pathlib.Path) -> str:
534    def add_file(self, path_or_file: str | Path) -> str:
535        """Insert a file from a path or a file-like object in the container.
536
537        Return the full path to reference in the content.
538
539        Arguments:
540
541            path_or_file -- str or Path or file-like
542
543        Return: str (URI)
544        """
545        if not self.container:
546            raise ValueError("Empty Container")
547        name = ""
548        # Folder for added files (FIXME hard-coded and copied)
549        manifest = self.manifest
550        medias = manifest.get_paths()
551        # uuid = str(uuid4())
552
553        if isinstance(path_or_file, (str, Path)):
554            path = Path(path_or_file)
555            extension = path.suffix.lower()
556            name = f"{path.stem}{extension}"
557            if posixpath.join("Pictures", name) in medias:
558                name = f"{path.stem}_{uuid4()}{extension}"
559        else:
560            path = None
561            name = getattr(path_or_file, "name", None)
562            if not name:
563                name = str(uuid4())
564        media_type, _encoding = guess_type(name)
565        if not media_type:
566            media_type = "application/octet-stream"
567        if manifest.get_media_type("Pictures/") is None:
568            manifest.add_full_path("Pictures/")
569        full_path = posixpath.join("Pictures", name)
570        if path is None:
571            self.container.set_part(full_path, path_or_file.read())
572        else:
573            self.container.set_part(full_path, path.read_bytes())
574        manifest.add_full_path(full_path, media_type)
575        return full_path

Insert a file from a path or a file-like object in the container.

Return the full path to reference in the content.

Arguments:

path_or_file -- str or Path or file-like

Return: str (URI)

clone: Document
577    @property
578    def clone(self) -> Document:
579        """Return an exact copy of the document.
580
581        Return: Document
582        """
583        clone = object.__new__(self.__class__)
584        for name in self.__dict__:
585            if name == "_Document__body":
586                setattr(clone, name, None)
587            elif name == "_Document__xmlparts":
588                setattr(clone, name, {})
589            elif name == "container":
590                if not self.container:
591                    raise ValueError("Empty Container")
592                setattr(clone, name, self.container.clone)
593            else:
594                value = deepcopy(getattr(self, name))
595                setattr(clone, name, value)
596        return clone

Return an exact copy of the document.

Return: Document

def save( self, target: str | pathlib.Path | _io.BytesIO | None = None, packaging: str = 'zip', pretty: bool = False, backup: bool = False) -> None:
598    def save(
599        self,
600        target: str | Path | io.BytesIO | None = None,
601        packaging: str = "zip",
602        pretty: bool = False,
603        backup: bool = False,
604    ) -> None:
605        """Save the document, at the same place it was opened or at the given
606        target path. Target can also be a file-like object. It can be saved
607        as a Zip file (default) or as files in a folder (for debugging
608        purpose). XML parts can be pretty printed.
609
610        Arguments:
611
612            target -- str or file-like object
613
614            packaging -- 'zip' or 'folder'
615
616            pretty -- bool
617
618            backup -- bool
619        """
620        if not self.container:
621            raise ValueError("Empty Container")
622        # Some advertising
623        self.meta.set_generator_default()
624        # Synchronize data with container
625        container = self.container
626        for path, part in self.__xmlparts.items():
627            if part is not None:
628                container.set_part(path, part.serialize(pretty))
629        # Save the container
630        container.save(target, packaging=packaging, backup=backup)

Save the document, at the same place it was opened or at the given target path. Target can also be a file-like object. It can be saved as a Zip file (default) or as files in a folder (for debugging purpose). XML parts can be pretty printed.

Arguments:

target -- str or file-like object

packaging -- 'zip' or 'folder'

pretty -- bool

backup -- bool
content: Content
632    @property
633    def content(self) -> Content:
634        content: Content | None = self.get_part(ODF_CONTENT)  # type:ignore
635        if content is None:
636            raise ValueError("Empty Content")
637        return content
styles: Styles
639    @property
640    def styles(self) -> Styles:
641        styles: Styles | None = self.get_part(ODF_STYLES)  # type:ignore
642        if styles is None:
643            raise ValueError("Empty Styles")
644        return styles
def get_styles( self, family: str | bytes = '', automatic: bool = False) -> list[Style | Element]:
648    def get_styles(
649        self,
650        family: str | bytes = "",
651        automatic: bool = False,
652    ) -> list[Style | Element]:
653        # compatibility with old versions:
654
655        if isinstance(family, bytes):
656            family = bytes_to_str(family)
657        return self.content.get_styles(family=family) + self.styles.get_styles(
658            family=family, automatic=automatic
659        )
def get_style( self, family: str, name_or_element: str | Style | None = None, display_name: str | None = None) -> Style | None:
661    def get_style(
662        self,
663        family: str,
664        name_or_element: str | Style | None = None,
665        display_name: str | None = None,
666    ) -> Style | None:
667        """Return the style uniquely identified by the name/family pair. If
668        the argument is already a style object, it will return it.
669
670        If the name is None, the default style is fetched.
671
672        If the name is not the internal name but the name you gave in a
673        desktop application, use display_name instead.
674
675        Arguments:
676
677            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
678                      'number', 'page-layout', 'master-page'
679
680            name -- str or Element or None
681
682            display_name -- str
683
684        Return: Style or None if not found.
685        """
686        # 1. content.xml
687        element = self.content.get_style(
688            family, name_or_element=name_or_element, display_name=display_name
689        )
690        if element is not None:
691            return element
692        # 2. styles.xml
693        return self.styles.get_style(
694            family,
695            name_or_element=name_or_element,
696            display_name=display_name,
697        )

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in a desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text',  'graphic', 'table', 'list',
          'number', 'page-layout', 'master-page'

name -- str or Element or None

display_name -- str

Return: Style or None if not found.

def insert_style( self, style: Style | str, name: str = '', automatic: bool = False, default: bool = False) -> Any:
831    def insert_style(  # noqa: C901
832        self,
833        style: Style | str,
834        name: str = "",
835        automatic: bool = False,
836        default: bool = False,
837    ) -> Any:
838        """Insert the given style object in the document, as required by the
839        style family and type.
840
841        The style is expected to be a common style with a name. In case it
842        was created with no name, the given can be set on the fly.
843
844        If automatic is True, the style will be inserted as an automatic
845        style.
846
847        If default is True, the style will be inserted as a default style and
848        would replace any existing default style of the same family. Any name
849        or display name would be ignored.
850
851        Automatic and default arguments are mutually exclusive.
852
853        All styles can't be used as default styles. Default styles are
854        allowed for the following families: paragraph, text, section, table,
855        table-column, table-row, table-cell, table-page, chart, drawing-page,
856        graphic, presentation, control and ruby.
857
858        Arguments:
859
860            style -- Style or str
861
862            name -- str
863
864            automatic -- bool
865
866            default -- bool
867
868        Return : style name -- str
869        """
870
871        # if style is a str, assume it is the Style definition
872        if isinstance(style, str):
873            style_element: Style = Element.from_tag(style)  # type: ignore
874        else:
875            style_element = style
876        if not isinstance(style_element, Element):
877            raise TypeError(f"Unknown Style type: '{style!r}'")
878
879        # Get family and name
880        family = self._pseudo_style_attribute(style_element, "family")
881        if not name:
882            name = self._pseudo_style_attribute(style_element, "name")
883
884        # Master page style
885        if family == "master-page":
886            existing, style_container = self._insert_style_get_master_page(family, name)
887        # Font face declarations
888        elif family == "font-face":
889            if default:
890                existing, style_container = self._insert_style_get_font_face_default(
891                    family, name
892                )
893            else:
894                existing, style_container = self._insert_style_get_font_face(
895                    family, name
896                )
897        # page layout style
898        elif family == "page-layout":
899            existing, style_container = self._insert_style_get_page_layout(family, name)
900        # Common style
901        elif family in FAMILY_ODF_STD or family in {"number"}:
902            existing, style_container = self._insert_style_standard(
903                style_element, name, family, automatic, default
904            )
905        elif not family and style_element.__class__.__name__ == "DrawFillImage":
906            # special case for 'draw:fill-image' pseudo style
907            existing, style_container = self._insert_style_get_draw_fill_image(name)
908        # Invalid style
909        else:
910            raise ValueError(
911                "Invalid style: "
912                f"{style_element}, tag:{style_element.tag}, family:{family}"
913            )
914
915        # Insert it!
916        if existing is not None:
917            style_container.delete(existing)
918        style_container.append(style_element)
919        return self._pseudo_style_attribute(style_element, "name")

Insert the given style object in the document, as required by the style family and type.

The style is expected to be a common style with a name. In case it was created with no name, the given can be set on the fly.

If automatic is True, the style will be inserted as an automatic style.

If default is True, the style will be inserted as a default style and would replace any existing default style of the same family. Any name or display name would be ignored.

Automatic and default arguments are mutually exclusive.

All styles can't be used as default styles. Default styles are allowed for the following families: paragraph, text, section, table, table-column, table-row, table-cell, table-page, chart, drawing-page, graphic, presentation, control and ruby.

Arguments:

style -- Style or str

name -- str

automatic -- bool

default -- bool

Return : style name -- str

def get_styled_elements(self, name: str = '') -> list[Element]:
921    def get_styled_elements(self, name: str = "") -> list[Element]:
922        """Brute-force to find paragraphs, tables, etc. using the given style
923        name (or all by default).
924
925        Arguments:
926
927            name -- str
928
929        Return: list
930        """
931        # Header, footer, etc. have styles too
932        return self.content.root.get_styled_elements(
933            name
934        ) + self.styles.root.get_styled_elements(name)

Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).

Arguments:

name -- str

Return: list

def show_styles( self, automatic: bool = True, common: bool = True, properties: bool = False) -> str:
936    def show_styles(
937        self,
938        automatic: bool = True,
939        common: bool = True,
940        properties: bool = False,
941    ) -> str:
942        infos = []
943        for style in self.get_styles():
944            try:
945                name = style.name  # type: ignore
946            except AttributeError:
947                print("--------------")
948                print(style.__class__)
949                print(style.serialize())
950                raise
951            if style.__class__.__name__ == "DrawFillImage":
952                family = ""
953            else:
954                family = str(style.family)  # type: ignore
955            parent = style.parent
956            is_auto = parent and parent.tag == "office:automatic-styles"
957            if is_auto and automatic is False or not is_auto and common is False:
958                continue
959            is_used = bool(self.get_styled_elements(name))
960            infos.append(
961                {
962                    "type": "auto  " if is_auto else "common",
963                    "used": "y" if is_used else "n",
964                    "family": family,
965                    "parent": self._pseudo_style_attribute(style, "parent_style") or "",
966                    "name": name or "",
967                    "display_name": self._pseudo_style_attribute(style, "display_name")
968                    or "",
969                    "properties": style.get_properties() if properties else None,  # type: ignore
970                }
971            )
972        if not infos:
973            return ""
974        # Sort by family and name
975        infos.sort(key=itemgetter("family", "name"))
976        # Show common and used first
977        infos.sort(key=itemgetter("type", "used"), reverse=True)
978        max_family = str(max([len(x["family"]) for x in infos]))  # type: ignore
979        max_parent = str(max([len(x["parent"]) for x in infos]))  # type: ignore
980        formater = (
981            "%(type)s used:%(used)s family:%(family)-0"
982            + max_family
983            + "s parent:%(parent)-0"
984            + max_parent
985            + "s name:%(name)s"
986        )
987        output = []
988        for info in infos:
989            line = formater % info
990            if info["display_name"]:
991                line += " display_name:" + info["display_name"]  # type: ignore
992            output.append(line)
993            if info["properties"]:
994                for name, value in info["properties"].items():  # type: ignore
995                    output.append(f"   - {name}: {value}")
996        output.append("")
997        return "\n".join(output)
def delete_styles(self) -> int:
 999    def delete_styles(self) -> int:
1000        """Remove all style information from content and all styles.
1001
1002        Return: number of deleted styles
1003        """
1004        # First remove references to styles
1005        for element in self.get_styled_elements():
1006            for attribute in (
1007                "text:style-name",
1008                "draw:style-name",
1009                "draw:text-style-name",
1010                "table:style-name",
1011                "style:page-layout-name",
1012            ):
1013                try:
1014                    element.del_attribute(attribute)
1015                except KeyError:
1016                    continue
1017        # Then remove supposedly orphaned styles
1018        deleted = 0
1019        for style in self.get_styles():
1020            if style.name is None:  # type: ignore
1021                # Don't delete default styles
1022                continue
1023            # elif type(style) is odf_master_page:
1024            #    # Don't suppress header and footer, just styling was removed
1025            #    continue
1026            style.delete()
1027            deleted += 1
1028        return deleted

Remove all style information from content and all styles.

Return: number of deleted styles

def merge_styles_from(self, document: Document) -> None:
1030    def merge_styles_from(self, document: Document) -> None:
1031        """Copy all the styles of a document into ourself.
1032
1033        Styles with the same type and name will be replaced, so only unique
1034        styles will be preserved.
1035        """
1036        manifest = self.manifest
1037        document_manifest = document.manifest
1038        for style in document.get_styles():
1039            tagname = style.tag
1040            family = self._pseudo_style_attribute(style, "family")
1041            stylename = style.name  # type: ignore
1042            container = style.parent
1043            container_name = container.tag  # type: ignore
1044            partname = container.parent.tag  # type: ignore
1045            # The destination part
1046            if partname == "office:document-styles":
1047                part: Content | Styles = self.styles
1048            elif partname == "office:document-content":
1049                part = self.content
1050            else:
1051                raise NotImplementedError(partname)
1052            # Implemented containers
1053            if container_name not in {
1054                "office:styles",
1055                "office:automatic-styles",
1056                "office:master-styles",
1057                "office:font-face-decls",
1058            }:
1059                raise NotImplementedError(container_name)
1060            dest = part.get_element(f"//{container_name}")
1061            # Implemented style types
1062            # if tagname not in registered_styles:
1063            #    raise NotImplementedError(tagname)
1064            duplicate = part.get_style(family, stylename)
1065            if duplicate is not None:
1066                duplicate.delete()
1067            dest.append(style)
1068            # Copy images from the header/footer
1069            if tagname == "style:master-page":
1070                query = "descendant::draw:image"
1071                for image in style.get_elements(query):
1072                    url = image.url  # type: ignore
1073                    part_url = document.get_part(url)
1074                    # Manually add the part to keep the name
1075                    self.set_part(url, part_url)  # type: ignore
1076                    media_type = document_manifest.get_media_type(url)
1077                    manifest.add_full_path(url, media_type)  # type: ignore
1078            # Copy images from the fill-image
1079            elif tagname == "draw:fill-image":
1080                url = style.url  # type: ignore
1081                part_url = document.get_part(url)
1082                self.set_part(url, part_url)  # type: ignore
1083                media_type = document_manifest.get_media_type(url)
1084                manifest.add_full_path(url, media_type)  # type: ignore

Copy all the styles of a document into ourself.

Styles with the same type and name will be replaced, so only unique styles will be preserved.

def add_page_break_style(self) -> None:
1086    def add_page_break_style(self) -> None:
1087        """Ensure that the document contains the style required for a manual page break.
1088
1089        Then a manual page break can be added to the document with:
1090            from paragraph import PageBreak
1091            ...
1092            document.body.append(PageBreak())
1093
1094        Note: this style uses the property 'fo:break-after', another
1095        possibility could be the property 'fo:break-before'
1096        """
1097        if existing := self.get_style(  # noqa: SIM102
1098            family="paragraph",
1099            name_or_element="odfdopagebreak",
1100        ):
1101            if properties := existing.get_properties():  # noqa: SIM102
1102                if properties["fo:break-after"] == "page":
1103                    return
1104        style = (
1105            '<style:style style:family="paragraph" style:parent-style-name="Standard" '
1106            'style:name="odfdopagebreak">'
1107            '<style:paragraph-properties fo:break-after="page"/></style:style>'
1108        )
1109        self.insert_style(style, automatic=False)

Ensure that the document contains the style required for a manual page break.

Then a manual page break can be added to the document with: from paragraph import PageBreak ... document.body.append(PageBreak())

Note: this style uses the property 'fo:break-after', another possibility could be the property 'fo:break-before'

class DrawFillImage(odfdo.DrawImage):
 85class DrawFillImage(DrawImage):
 86    _tag = "draw:fill-image"
 87    _properties: tuple[PropDef, ...] = (
 88        PropDef("display_name", "draw:display-name"),
 89        PropDef("name", "draw:name"),
 90        PropDef("height", "svg:height"),
 91        PropDef("width", "svg:width"),
 92    )
 93
 94    def __init__(
 95        self,
 96        name: str | None = None,
 97        display_name: str | None = None,
 98        height: str | None = None,
 99        width: str | None = None,
100        **kwargs: Any,
101    ) -> None:
102        """The "draw:fill-image" element specifies a link to a bitmap
103        resource. Fill image are not available as automatic styles.
104        The "draw:fill-image" element is usable within the following element:
105        "office:styles"
106
107        Arguments:
108
109            name -- str
110
111            display_name -- str
112
113            height -- str
114
115            width -- str
116        """
117        super().__init__(**kwargs)
118        if self._do_init:
119            self.name = name
120            self.display_name = display_name
121            self.height = height
122            self.width = width

The "draw:image" element represents an image. An image can be either:

  • A link to an external resource or
  • Embedded in the document (Not implemented in this version)

Warning: image elements must be stored in a frame "draw:frame", see Frame().

DrawFillImage( name: str | None = None, display_name: str | None = None, height: str | None = None, width: str | None = None, **kwargs: Any)
 94    def __init__(
 95        self,
 96        name: str | None = None,
 97        display_name: str | None = None,
 98        height: str | None = None,
 99        width: str | None = None,
100        **kwargs: Any,
101    ) -> None:
102        """The "draw:fill-image" element specifies a link to a bitmap
103        resource. Fill image are not available as automatic styles.
104        The "draw:fill-image" element is usable within the following element:
105        "office:styles"
106
107        Arguments:
108
109            name -- str
110
111            display_name -- str
112
113            height -- str
114
115            width -- str
116        """
117        super().__init__(**kwargs)
118        if self._do_init:
119            self.name = name
120            self.display_name = display_name
121            self.height = height
122            self.width = width

The "draw:fill-image" element specifies a link to a bitmap resource. Fill image are not available as automatic styles. The "draw:fill-image" element is usable within the following element: "office:styles"

Arguments:

name -- str

display_name -- str

height -- str

width -- str
display_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
height: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
width: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
DrawImage
url
type
show
actuate
filter_name
class DrawGroup(odfdo.Element, odfdo.frame.AnchorMix, odfdo.frame.ZMix, odfdo.frame.PosMix):
315class DrawGroup(Element, AnchorMix, ZMix, PosMix):
316    """The DrawGroup "draw:g" element represents a group of drawing shapes.
317
318    Warning: implementation is currently minimal.
319
320    Drawing shapes contained by a "draw:g" element that is itself
321    contained by a "draw:a" element, act as hyperlinks using the
322    xlink:href attribute of the containing "draw:a" element. If the
323    included drawing shapes are themselves contained within "draw:a"
324    elements, then the xlink:href attributes of those "draw:a" elements
325    act as the hyperlink information for the shapes they contain.
326
327    The "draw:g" element has the following attributes: draw:caption-id,
328    draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index,
329    presentation:class-names, presentation:style-name, svg:y,
330    table:end-cell-address, table:end-x, table:end-y,
331    table:table-background, text:anchor-page-number, text:anchor-type,
332    and xml:id.
333
334    The "draw:g" element has the following child elements: "dr3d:scene",
335    "draw:a", "draw:caption", "draw:circle", "draw:connector",
336    "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame",
337    "draw:g", "draw:glue-point", "draw:line", "draw:measure",
338    "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline",
339    "draw:rect", "draw:regular-polygon", "office:event-listeners",
340    "svg:desc" and "svg:title".
341    """
342
343    _tag = "draw:g"
344    _properties: tuple[PropDef, ...] = (
345        PropDef("draw_id", "draw:id"),
346        PropDef("caption_id", "draw:caption-id"),
347        PropDef("draw_class_names", "draw:class-names"),
348        PropDef("name", "draw:name"),
349        PropDef("style", "draw:style-name"),
350        # ('z_index', 'draw:z-index'),
351        PropDef("presentation_class_names", "presentation:class-names"),
352        PropDef("presentation_style", "presentation:style-name"),
353        PropDef("table_end_cell", "table:end-cell-address"),
354        PropDef("table_end_x", "table:end-x"),
355        PropDef("table_end_y", "table:end-y"),
356        PropDef("table_background", "table:table-background"),
357        # ('anchor_page', 'text:anchor-page-number'),
358        # ('anchor_type', 'text:anchor-type'),
359        PropDef("xml_id", "xml:id"),
360        PropDef("pos_x", "svg:x"),
361        PropDef("pos_y", "svg:y"),
362    )
363
364    def __init__(
365        self,
366        name: str | None = None,
367        draw_id: str | None = None,
368        style: str | None = None,
369        position: tuple | None = None,
370        z_index: int = 0,
371        anchor_type: str | None = None,
372        anchor_page: int | None = None,
373        presentation_style: str | None = None,
374        **kwargs: Any,
375    ) -> None:
376        super().__init__(**kwargs)
377        if self._do_init:
378            if z_index is not None:
379                self.z_index = z_index
380            if name:
381                self.name = name
382            if draw_id is not None:
383                self.draw_id = draw_id
384            if style is not None:
385                self.style = style
386            if position is not None:
387                self.position = position
388            if anchor_type:
389                self.anchor_type = anchor_type
390            if anchor_page is not None:
391                self.anchor_page = anchor_page
392            if presentation_style is not None:
393                self.presentation_style = presentation_style

The DrawGroup "draw:g" element represents a group of drawing shapes.

Warning: implementation is currently minimal.

Drawing shapes contained by a "draw:g" element that is itself contained by a "draw:a" element, act as hyperlinks using the xlink:href attribute of the containing "draw:a" element. If the included drawing shapes are themselves contained within "draw:a" elements, then the xlink:href attributes of those "draw:a" elements act as the hyperlink information for the shapes they contain.

The "draw:g" element has the following attributes: draw:caption-id, draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, presentation:class-names, presentation:style-name, svg:y, table:end-cell-address, table:end-x, table:end-y, table:table-background, text:anchor-page-number, text:anchor-type, and xml:id.

The "draw:g" element has the following child elements: "dr3d:scene", "draw:a", "draw:caption", "draw:circle", "draw:connector", "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", "draw:g", "draw:glue-point", "draw:line", "draw:measure", "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", "draw:rect", "draw:regular-polygon", "office:event-listeners", "svg:desc" and "svg:title".

DrawGroup( name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, z_index: int = 0, anchor_type: str | None = None, anchor_page: int | None = None, presentation_style: str | None = None, **kwargs: Any)
364    def __init__(
365        self,
366        name: str | None = None,
367        draw_id: str | None = None,
368        style: str | None = None,
369        position: tuple | None = None,
370        z_index: int = 0,
371        anchor_type: str | None = None,
372        anchor_page: int | None = None,
373        presentation_style: str | None = None,
374        **kwargs: Any,
375    ) -> None:
376        super().__init__(**kwargs)
377        if self._do_init:
378            if z_index is not None:
379                self.z_index = z_index
380            if name:
381                self.name = name
382            if draw_id is not None:
383                self.draw_id = draw_id
384            if style is not None:
385                self.style = style
386            if position is not None:
387                self.position = position
388            if anchor_type:
389                self.anchor_type = anchor_type
390            if anchor_page is not None:
391                self.anchor_page = anchor_page
392            if presentation_style is not None:
393                self.presentation_style = presentation_style
draw_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
caption_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
draw_class_names: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_class_names: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_end_cell: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_end_x: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_end_y: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_background: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
xml_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_x: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_y: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.AnchorMix
ANCHOR_VALUE_CHOICE
anchor_type
anchor_page
odfdo.frame.ZMix
z_index
odfdo.frame.PosMix
position
class DrawImage(odfdo.Element):
31class DrawImage(Element):
32    """The "draw:image" element represents an image. An image can be
33    either:
34    - A link to an external resource or
35    - Embedded in the document (Not implemented in this version)
36
37    Warning: image elements must be stored in a frame "draw:frame",
38    see Frame().
39    """
40
41    _tag = "draw:image"
42    _properties: tuple[PropDef, ...] = (
43        PropDef("url", "xlink:href"),
44        PropDef("type", "xlink:type"),
45        PropDef("show", "xlink:show"),
46        PropDef("actuate", "xlink:actuate"),
47        PropDef("filter_name", "draw:filter-name"),
48    )
49
50    def __init__(
51        self,
52        url: str = "",
53        xlink_type: str = "simple",
54        show: str = "embed",
55        actuate: str = "onLoad",
56        filter_name: str | None = None,
57        **kwargs: Any,
58    ) -> None:
59        """Initialisation of an DrawImage.
60
61        Arguments:
62
63            url -- str
64
65            type -- str
66
67            show -- str
68
69            actuate -- str
70
71            filter_name -- str
72        """
73        super().__init__(**kwargs)
74        if self._do_init:
75            self.url = url
76            self.type = xlink_type
77            self.show = show
78            self.actuate = actuate
79            self.filter_name = filter_name

The "draw:image" element represents an image. An image can be either:

  • A link to an external resource or
  • Embedded in the document (Not implemented in this version)

Warning: image elements must be stored in a frame "draw:frame", see Frame().

DrawImage( url: str = '', xlink_type: str = 'simple', show: str = 'embed', actuate: str = 'onLoad', filter_name: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        url: str = "",
53        xlink_type: str = "simple",
54        show: str = "embed",
55        actuate: str = "onLoad",
56        filter_name: str | None = None,
57        **kwargs: Any,
58    ) -> None:
59        """Initialisation of an DrawImage.
60
61        Arguments:
62
63            url -- str
64
65            type -- str
66
67            show -- str
68
69            actuate -- str
70
71            filter_name -- str
72        """
73        super().__init__(**kwargs)
74        if self._do_init:
75            self.url = url
76            self.type = xlink_type
77            self.show = show
78            self.actuate = actuate
79            self.filter_name = filter_name

Initialisation of an DrawImage.

Arguments:

url -- str

type -- str

show -- str

actuate -- str

filter_name -- str
url: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
show: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
actuate: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
filter_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class DrawPage(odfdo.Element):
 33class DrawPage(Element):
 34    """ODF draw page "draw:page", for pages of presentation and drawings."""
 35
 36    _tag = "draw:page"
 37    _properties = (
 38        PropDef("name", "draw:name"),
 39        PropDef("draw_id", "draw:id"),
 40        PropDef("master_page", "draw:master-page-name"),
 41        PropDef(
 42            "presentation_page_layout", "presentation:presentation-page-layout-name"
 43        ),
 44        PropDef("style", "draw:style-name"),
 45    )
 46
 47    def __init__(
 48        self,
 49        draw_id: str | None = None,
 50        name: str | None = None,
 51        master_page: str | None = None,
 52        presentation_page_layout: str | None = None,
 53        style: str | None = None,
 54        **kwargs: Any,
 55    ) -> None:
 56        """
 57        Arguments:
 58
 59            draw_id -- str
 60
 61            name -- str
 62
 63            master_page -- str
 64
 65            presentation_page_layout -- str
 66
 67            style -- str
 68        """
 69        super().__init__(**kwargs)
 70        if self._do_init:
 71            if draw_id:
 72                self.draw_id = draw_id
 73            if name:
 74                self.name = name
 75            if master_page:
 76                self.master_page = master_page
 77            if presentation_page_layout:
 78                self.presentation_page_layout = presentation_page_layout
 79            if style:
 80                self.style = style
 81
 82    def set_transition(
 83        self,
 84        smil_type: str,
 85        subtype: str | None = None,
 86        dur: str = "2s",
 87    ) -> None:
 88        # Create the new animation
 89        anim_page = AnimPar(presentation_node_type="timing-root")
 90        anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin")
 91        transition = AnimTransFilter(
 92            smil_dur=dur, smil_type=smil_type, smil_subtype=subtype
 93        )
 94        anim_page.append(anim_begin)
 95        anim_begin.append(transition)
 96
 97        # Replace when already a transition:
 98        #   anim:seq => After the frame's transition
 99        #   cf page 349 of OpenDocument-v1.0-os.pdf
100        #   Conclusion: We must delete the first child 'anim:par'
101        existing = self.get_element("anim:par")
102        if existing:
103            self.delete(existing)
104        self.append(anim_page)
105
106    def get_shapes(self) -> list[Element]:
107        query = "(descendant::" + "|descendant::".join(registered_shapes) + ")"
108        return self.get_elements(query)
109
110    def get_formatted_text(self, context: dict | None = None) -> str:
111        result: list[str] = []
112        for child in self.children:
113            if child.tag == "presentation:notes":
114                # No need for an advanced odf_notes.get_formatted_text()
115                # because the text seems to be only contained in paragraphs
116                # and frames, that we already handle
117                for sub_child in child.children:
118                    result.append(sub_child.get_formatted_text(context))
119                result.append("\n")
120            result.append(child.get_formatted_text(context))
121        result.append("\n")
122        return "".join(result)

ODF draw page "draw:page", for pages of presentation and drawings.

DrawPage( draw_id: str | None = None, name: str | None = None, master_page: str | None = None, presentation_page_layout: str | None = None, style: str | None = None, **kwargs: Any)
47    def __init__(
48        self,
49        draw_id: str | None = None,
50        name: str | None = None,
51        master_page: str | None = None,
52        presentation_page_layout: str | None = None,
53        style: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        """
57        Arguments:
58
59            draw_id -- str
60
61            name -- str
62
63            master_page -- str
64
65            presentation_page_layout -- str
66
67            style -- str
68        """
69        super().__init__(**kwargs)
70        if self._do_init:
71            if draw_id:
72                self.draw_id = draw_id
73            if name:
74                self.name = name
75            if master_page:
76                self.master_page = master_page
77            if presentation_page_layout:
78                self.presentation_page_layout = presentation_page_layout
79            if style:
80                self.style = style

Arguments:

draw_id -- str

name -- str

master_page -- str

presentation_page_layout -- str

style -- str
def set_transition( self, smil_type: str, subtype: str | None = None, dur: str = '2s') -> None:
 82    def set_transition(
 83        self,
 84        smil_type: str,
 85        subtype: str | None = None,
 86        dur: str = "2s",
 87    ) -> None:
 88        # Create the new animation
 89        anim_page = AnimPar(presentation_node_type="timing-root")
 90        anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin")
 91        transition = AnimTransFilter(
 92            smil_dur=dur, smil_type=smil_type, smil_subtype=subtype
 93        )
 94        anim_page.append(anim_begin)
 95        anim_begin.append(transition)
 96
 97        # Replace when already a transition:
 98        #   anim:seq => After the frame's transition
 99        #   cf page 349 of OpenDocument-v1.0-os.pdf
100        #   Conclusion: We must delete the first child 'anim:par'
101        existing = self.get_element("anim:par")
102        if existing:
103            self.delete(existing)
104        self.append(anim_page)
def get_shapes(self) -> list[Element]:
106    def get_shapes(self) -> list[Element]:
107        query = "(descendant::" + "|descendant::".join(registered_shapes) + ")"
108        return self.get_elements(query)
def get_formatted_text(self, context: dict | None = None) -> str:
110    def get_formatted_text(self, context: dict | None = None) -> str:
111        result: list[str] = []
112        for child in self.children:
113            if child.tag == "presentation:notes":
114                # No need for an advanced odf_notes.get_formatted_text()
115                # because the text seems to be only contained in paragraphs
116                # and frames, that we already handle
117                for sub_child in child.children:
118                    result.append(sub_child.get_formatted_text(context))
119                result.append("\n")
120            result.append(child.get_formatted_text(context))
121        result.append("\n")
122        return "".join(result)

This function should return a beautiful version of the text.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
draw_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
master_page: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_page_layout: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Element(odfdo.utils.cached_element.CachedElement):
 306class Element(CachedElement):
 307    """Super class of all ODF classes.
 308
 309    Representation of an XML element. Abstraction of the XML library behind.
 310    """
 311
 312    _tag: str = ""
 313    _caching: bool = False
 314    _properties: tuple[PropDef, ...] = ()
 315
 316    def __init__(self, **kwargs: Any) -> None:
 317        tag_or_elem = kwargs.pop("tag_or_elem", None)
 318        if tag_or_elem is None:
 319            # Instance for newly created object: create new lxml element and
 320            # continue by subclass __init__
 321            # If the tag key word exists, make a custom element
 322            self._do_init = True
 323            tag = kwargs.pop("tag", self._tag)
 324            self.__element = self.make_etree_element(tag)
 325        else:
 326            # called with an existing lxml element, sould be a result of
 327            # from_tag() casting, do not execute the subclass __init__
 328            if not isinstance(tag_or_elem, _Element):
 329                raise TypeError(f'"{type(tag_or_elem)}" is not an element node')
 330            self._do_init = False
 331            self.__element = tag_or_elem
 332
 333    def __repr__(self) -> str:
 334        return f"<{self.__class__.__name__} tag={self.tag}>"
 335
 336    def __str__(self) -> str:
 337        return self.text_recursive
 338
 339    @classmethod
 340    def from_tag(cls, tag_or_elem: str | _Element) -> Element:
 341        """Element class and subclass factory.
 342
 343        Turn an lxml Element or ODF string tag into an ODF XML Element
 344        of the relevant class.
 345
 346        Arguments:
 347
 348            tag_or_elem -- ODF str tag or lxml.Element
 349
 350        Return: Element (or subclass) instance
 351        """
 352        if isinstance(tag_or_elem, str):
 353            # assume the argument is a prefix:name tag
 354            elem = cls.make_etree_element(tag_or_elem)
 355        else:
 356            elem = tag_or_elem
 357        klass = _class_registry.get(elem.tag, cls)
 358        return klass(tag_or_elem=elem)
 359
 360    @classmethod
 361    def from_tag_for_clone(
 362        cls: type,
 363        tree_element: _Element,
 364        cache: tuple | None,
 365    ) -> Element:
 366        tag = to_str(tree_element.tag)
 367        klass = _class_registry.get(tag, cls)
 368        element = klass(tag_or_elem=tree_element)
 369        if cache and element._caching:
 370            element._tmap = cache[0]
 371            element._cmap = cache[1]
 372            if len(cache) == 3:
 373                element._rmap = cache[2]
 374        return element
 375
 376    @staticmethod
 377    def make_etree_element(tag: str) -> _Element:
 378        if not isinstance(tag, str):
 379            raise TypeError(f"Tag is not str: {tag!r}")
 380        tag = tag.strip()
 381        if not tag:
 382            raise ValueError("Tag is empty")
 383        if "<" not in tag:
 384            # Qualified name
 385            # XXX don't build the element from scratch or lxml will pollute with
 386            # repeated namespace declarations
 387            tag = f"<{tag}/>"
 388        # XML fragment
 389        root = fromstring(NAMESPACES_XML % str_to_bytes(tag))
 390        return root[0]
 391
 392    @staticmethod
 393    def _generic_attrib_getter(attr_name: str, family: str | None = None) -> Callable:
 394        name = _get_lxml_tag(attr_name)
 395
 396        def getter(self: Element) -> str | bool | None:
 397            try:
 398                if family and self.family != family:  # type: ignore
 399                    return None
 400            except AttributeError:
 401                return None
 402            value = self.__element.get(name)
 403            if value is None:
 404                return None
 405            elif value in ("true", "false"):
 406                return Boolean.decode(value)
 407            return str(value)
 408
 409        return getter
 410
 411    @staticmethod
 412    def _generic_attrib_setter(attr_name: str, family: str | None = None) -> Callable:
 413        name = _get_lxml_tag(attr_name)
 414
 415        def setter(self: Element, value: Any) -> None:
 416            try:
 417                if family and self.family != family:  # type: ignore
 418                    return None
 419            except AttributeError:
 420                return None
 421            if value is None:
 422                with contextlib.suppress(KeyError):
 423                    del self.__element.attrib[name]
 424                return
 425            if isinstance(value, bool):
 426                value = Boolean.encode(value)
 427            self.__element.set(name, str(value))
 428
 429        return setter
 430
 431    @classmethod
 432    def _define_attribut_property(cls: type[Element]) -> None:
 433        for prop in cls._properties:
 434            setattr(
 435                cls,
 436                prop.name,
 437                property(
 438                    cls._generic_attrib_getter(prop.attr, prop.family or None),
 439                    cls._generic_attrib_setter(prop.attr, prop.family or None),
 440                    None,
 441                    f"Get/set the attribute {prop.attr}",
 442                ),
 443            )
 444
 445    @staticmethod
 446    def _make_before_regex(
 447        before: str | None,
 448        after: str | None,
 449    ) -> re.Pattern:
 450        # 1) before xor after is not None
 451        if before is not None:
 452            return re.compile(before)
 453        else:
 454            if after is None:
 455                raise ValueError("Both 'before' and 'after' are None")
 456            return re.compile(after)
 457
 458    @staticmethod
 459    def _search_negative_position(
 460        xpath_result: list,
 461        regex: re.Pattern,
 462    ) -> tuple[str, re.Match]:
 463        # Found the last text that matches the regex
 464        text = None
 465        for a_text in xpath_result:
 466            if regex.search(str(a_text)) is not None:
 467                text = a_text
 468        if text is None:
 469            raise ValueError(f"Text not found: '{xpath_result}'")
 470        if not isinstance(text, str):
 471            raise TypeError(f"Text not found or text not of type str: '{text}'")
 472        return text, list(regex.finditer(text))[-1]
 473
 474    @staticmethod
 475    def _search_positive_position(
 476        xpath_result: list,
 477        regex: re.Pattern,
 478        position: int,
 479    ) -> tuple[str, re.Match]:
 480        # Found the last text that matches the regex
 481        count = 0
 482        for text in xpath_result:
 483            found_nb = len(regex.findall(str(text)))
 484            if found_nb + count >= position + 1:
 485                break
 486            count += found_nb
 487        else:
 488            raise ValueError(f"Text not found: '{xpath_result}'")
 489        if not isinstance(text, str):
 490            raise TypeError(f"Text not found or text not of type str: '{text}'")
 491        return text, list(regex.finditer(text))[position - count]
 492
 493    def _insert_before_after(
 494        self,
 495        current: _Element,
 496        element: _Element,
 497        before: str | None,
 498        after: str | None,
 499        position: int,
 500        xpath_text: XPath,
 501    ) -> tuple[int, str]:
 502        regex = self._make_before_regex(before, after)
 503        xpath_result = xpath_text(current)
 504        if not isinstance(xpath_result, list):
 505            raise TypeError("Bad XPath result")
 506        # position = -1
 507        if position < 0:
 508            text, sre = self._search_negative_position(xpath_result, regex)
 509        # position >= 0
 510        else:
 511            text, sre = self._search_positive_position(xpath_result, regex, position)
 512        # Compute pos
 513        if before is None:
 514            pos = sre.end()
 515        else:
 516            pos = sre.start()
 517        return pos, text
 518
 519    def _insert_find_text(
 520        self,
 521        current: _Element,
 522        element: _Element,
 523        before: str | None,
 524        after: str | None,
 525        position: int,
 526        xpath_text: XPath,
 527    ) -> tuple[int, str]:
 528        # Find the text
 529        xpath_result = xpath_text(current)
 530        if not isinstance(xpath_result, list):
 531            raise TypeError("Bad XPath result")
 532        count = 0
 533        for text in xpath_result:
 534            if not isinstance(text, str):
 535                continue
 536            found_nb = len(text)
 537            if found_nb + count >= position:
 538                break
 539            count += found_nb
 540        else:
 541            raise ValueError("Text not found")
 542        # We insert before the character
 543        pos = position - count
 544        return pos, text
 545
 546    def _insert(
 547        self,
 548        element: Element,
 549        before: str | None = None,
 550        after: str | None = None,
 551        position: int = 0,
 552        main_text: bool = False,
 553    ) -> None:
 554        """Insert an element before or after the characters in the text which
 555        match the regex before/after.
 556
 557        When the regex matches more of one part of the text, position can be
 558        set to choice which part must be used. If before and after are None,
 559        we use only position that is the number of characters. If position is
 560        positive and before=after=None, we insert before the position
 561        character. But if position=-1, we insert after the last character.
 562
 563
 564        Arguments:
 565
 566        element -- Element
 567
 568        before -- str regex
 569
 570        after -- str regex
 571
 572        position -- int
 573        """
 574        # not implemented: if main_text is True, filter out the annotations texts in computation.
 575        current = self.__element
 576        xelement = element.__element
 577
 578        if main_text:
 579            xpath_text = _xpath_text_main_descendant
 580        else:
 581            xpath_text = _xpath_text_descendant
 582
 583        # 1) before xor after is not None
 584        if (before is not None) ^ (after is not None):
 585            pos, text = self._insert_before_after(
 586                current,
 587                xelement,
 588                before,
 589                after,
 590                position,
 591                xpath_text,
 592            )
 593        # 2) before=after=None => only with position
 594        elif before is None and after is None:
 595            # Hack if position is negative => quickly
 596            if position < 0:
 597                current.append(xelement)
 598                return
 599            pos, text = self._insert_find_text(
 600                current,
 601                xelement,
 602                before,
 603                after,
 604                position,
 605                xpath_text,
 606            )
 607        else:
 608            raise ValueError("bad combination of arguments")
 609
 610        # Compute new texts
 611        text_before = text[:pos] if text[:pos] else None
 612        text_after = text[pos:] if text[pos:] else None
 613
 614        # Insert!
 615        parent = text.getparent()  # type: ignore
 616        if text.is_text:  # type: ignore
 617            parent.text = text_before
 618            element.tail = text_after
 619            parent.insert(0, xelement)
 620        else:
 621            parent.addnext(xelement)
 622            parent.tail = text_before
 623            element.tail = text_after
 624
 625    def _insert_between(  # noqa: C901
 626        self,
 627        element: Element,
 628        from_: str,
 629        to: str,
 630    ) -> None:
 631        """Insert the given empty element to wrap the text beginning with
 632        "from_" and ending with "to".
 633
 634        Example 1: '<p>toto tata titi</p>
 635
 636        We want to insert a link around "tata".
 637
 638        Result 1: '<p>toto <a>tata</a> titi</p>
 639
 640        Example 2: '<p><span>toto</span> tata titi</p>
 641
 642        We want to insert a link around "tata".
 643
 644        Result 2: '<p><span>toto</span> <a>tata</a> titi</p>
 645
 646        Example 3: '<p>toto <span> tata </span> titi</p>'
 647
 648        We want to insert a link from "tata" to "titi" included.
 649
 650        Result 3: '<p>toto <span> </span>'
 651                  '<a><span>tata </span> titi</a></p>'
 652
 653        Example 4: '<p>toto <span>tata titi</span> tutu</p>'
 654
 655        We want to insert a link from "titi" to "tutu"
 656
 657        Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>'
 658                  '<a> tutu</a></p>'
 659
 660        Example 5: '<p>toto <span>tata titi</span> '
 661                   '<span>tutu tyty</span></p>'
 662
 663        We want to insert a link from "titi" to "tutu"
 664
 665        Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> '
 666                  '<a> <span>tutu</span></a><span> tyty</span></p>'
 667        """
 668        current = self.__element
 669        wrapper = element.__element
 670
 671        xpath_result = _xpath_text_descendant(current)
 672        if not isinstance(xpath_result, list):
 673            raise TypeError("Bad XPath result")
 674
 675        for text in xpath_result:
 676            if not isinstance(text, str):
 677                raise TypeError("Text not found or text not of type str")
 678            if from_ not in text:
 679                continue
 680            from_index = text.index(from_)
 681            text_before = text[:from_index]
 682            text_after = text[from_index:]
 683            from_container = text.getparent()  # type: ignore
 684            if not isinstance(from_container, _Element):
 685                raise TypeError("Bad XPath result")
 686            # Include from_index to match a single word
 687            to_index = text.find(to, from_index)
 688            if to_index >= 0:
 689                # Simple case: "from" and "to" in the same element
 690                to_end = to_index + len(to)
 691                if text.is_text:  # type: ignore
 692                    from_container.text = text_before
 693                    wrapper.text = text[to_index:to_end]
 694                    wrapper.tail = text[to_end:]
 695                    from_container.insert(0, wrapper)
 696                else:
 697                    from_container.tail = text_before
 698                    wrapper.text = text[to_index:to_end]
 699                    wrapper.tail = text[to_end:]
 700                    parent = from_container.getparent()
 701                    index = parent.index(from_container)  # type: ignore
 702                    parent.insert(index + 1, wrapper)  # type: ignore
 703                return
 704            else:
 705                # Exit to the second part where we search for the end text
 706                break
 707        else:
 708            raise ValueError("Start text not found")
 709
 710        # The container is split in two
 711        container2 = deepcopy(from_container)
 712        if text.is_text:  # type: ignore
 713            from_container.text = text_before
 714            from_container.tail = None
 715            container2.text = text_after
 716            from_container.tail = None
 717        else:
 718            from_container.tail = text_before
 719            container2.tail = text_after
 720        # Stack the copy into the surrounding element
 721        wrapper.append(container2)
 722        parent = from_container.getparent()
 723        index = parent.index(from_container)  # type: ignore
 724        parent.insert(index + 1, wrapper)  # type: ignore
 725
 726        xpath_result = _xpath_text_descendant(wrapper)
 727        if not isinstance(xpath_result, list):
 728            raise TypeError("Bad XPath result")
 729
 730        for text in xpath_result:
 731            if not isinstance(text, str):
 732                raise TypeError("Text not found or text not of type str")
 733            if to not in text:
 734                continue
 735            to_end = text.index(to) + len(to)
 736            text_before = text[:to_end]
 737            text_after = text[to_end:]
 738            container_to = text.getparent()  # type: ignore
 739            if not isinstance(container_to, _Element):
 740                raise TypeError("Bad XPath result")
 741            if text.is_text:  # type: ignore
 742                container_to.text = text_before
 743                container_to.tail = text_after
 744            else:
 745                container_to.tail = text_before
 746                next_one = container_to.getnext()
 747                if next_one is None:
 748                    next_one = container_to.getparent()
 749                next_one.tail = text_after  # type: ignore
 750            return
 751        raise ValueError("End text not found")
 752
 753    @property
 754    def tag(self) -> str:
 755        """Get/set the underlying xml tag with the given qualified name.
 756
 757        Warning: direct change of tag does not change the element class.
 758
 759        Arguments:
 760
 761            qname -- str (e.g. "text:span")
 762        """
 763        return _get_prefixed_name(self.__element.tag)
 764
 765    @tag.setter
 766    def tag(self, qname: str) -> None:
 767        self.__element.tag = _get_lxml_tag(qname)
 768
 769    def elements_repeated_sequence(
 770        self,
 771        xpath_instance: XPath,
 772        name: str,
 773    ) -> list[tuple[int, int]]:
 774        """Utility method for table module."""
 775        lxml_tag = _get_lxml_tag_or_name(name)
 776        element = self.__element
 777        sub_elements = xpath_instance(element)
 778        if not isinstance(sub_elements, list):
 779            raise TypeError("Bad XPath result.")
 780        result: list[tuple[int, int]] = []
 781        idx = -1
 782        for sub_element in sub_elements:
 783            if not isinstance(sub_element, _Element):
 784                continue
 785            idx += 1
 786            value = sub_element.get(lxml_tag)
 787            if value is None:
 788                result.append((idx, 1))
 789                continue
 790            try:
 791                int_value = int(value)
 792            except ValueError:
 793                int_value = 1
 794            result.append((idx, max(int_value, 1)))
 795        return result
 796
 797    def get_elements(self, xpath_query: XPath | str) -> list[Element]:
 798        cache: tuple | None = None
 799        element = self.__element
 800        if isinstance(xpath_query, str):
 801            new_xpath_query = xpath_compile(xpath_query)
 802            result = new_xpath_query(element)
 803        else:
 804            result = xpath_query(element)
 805        if not isinstance(result, list):
 806            raise TypeError("Bad XPath result")
 807
 808        if hasattr(self, "_tmap"):
 809            if hasattr(self, "_rmap"):
 810                cache = (self._tmap, self._cmap, self._rmap)
 811            else:
 812                cache = (self._tmap, self._cmap)
 813        return [
 814            Element.from_tag_for_clone(e, cache)
 815            for e in result
 816            if isinstance(e, _Element)
 817        ]
 818
 819    # fixme : need original get_element as wrapper of get_elements
 820
 821    def get_element(self, xpath_query: XPath | str) -> Element | None:
 822        element = self.__element
 823        result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES)
 824        if result:
 825            return Element.from_tag(result[0])  # type:ignore
 826        return None
 827
 828    def _get_element_idx(self, xpath_query: XPath | str, idx: int) -> Element | None:
 829        element = self.__element
 830        result = element.xpath(f"({xpath_query})[{idx + 1}]", namespaces=ODF_NAMESPACES)
 831        if result:
 832            return Element.from_tag(result[0])  # type:ignore
 833        return None
 834
 835    def _get_element_idx2(self, xpath_instance: XPath, idx: int) -> Element | None:
 836        element = self.__element
 837        result = xpath_instance(element, idx=idx + 1)
 838        if result:
 839            return Element.from_tag(result[0])  # type:ignore
 840        return None
 841
 842    @property
 843    def attributes(self) -> dict[str, str]:
 844        return {
 845            _get_prefixed_name(str(key)): str(value)
 846            for key, value in self.__element.attrib.items()
 847        }
 848
 849    def get_attribute(self, name: str) -> str | bool | None:
 850        """Return the attribute value as type str | bool | None."""
 851        element = self.__element
 852        lxml_tag = _get_lxml_tag_or_name(name)
 853        value = element.get(lxml_tag)
 854        if value is None:
 855            return None
 856        elif value in ("true", "false"):
 857            return Boolean.decode(value)
 858        return str(value)
 859
 860    def get_attribute_integer(self, name: str) -> int | None:
 861        """Return either the attribute as type int, or None."""
 862        element = self.__element
 863        lxml_tag = _get_lxml_tag_or_name(name)
 864        value = element.get(lxml_tag)
 865        if value is None:
 866            return None
 867        try:
 868            return int(value)
 869        except ValueError:
 870            return None
 871
 872    def get_attribute_string(self, name: str) -> str | None:
 873        """Return either the attribute as type str, or None."""
 874        element = self.__element
 875        lxml_tag = _get_lxml_tag_or_name(name)
 876        value = element.get(lxml_tag)
 877        if value is None:
 878            return None
 879        return str(value)
 880
 881    def set_attribute(
 882        self, name: str, value: bool | str | tuple[int, int, int] | None
 883    ) -> None:
 884        if name in ODF_COLOR_PROPERTY:
 885            if isinstance(value, bool):
 886                raise TypeError(f"Wrong color type {value!r}")
 887            if value != "transparent":
 888                value = hexa_color(value)
 889        element = self.__element
 890        lxml_tag = _get_lxml_tag_or_name(name)
 891        if isinstance(value, bool):
 892            value = Boolean.encode(value)
 893        elif value is None:
 894            with contextlib.suppress(KeyError):
 895                del element.attrib[lxml_tag]
 896            return
 897        element.set(lxml_tag, str(value))
 898
 899    def set_style_attribute(self, name: str, value: Element | str) -> None:
 900        """Shortcut to accept a style object as a value."""
 901        if isinstance(value, Element):
 902            value = str(value.name)  # type:ignore
 903        return self.set_attribute(name, value)
 904
 905    def del_attribute(self, name: str) -> None:
 906        element = self.__element
 907        lxml_tag = _get_lxml_tag_or_name(name)
 908        del element.attrib[lxml_tag]
 909
 910    @property
 911    def text(self) -> str:
 912        """Get / set the text content of the element."""
 913        return self.__element.text or ""
 914
 915    @text.setter
 916    def text(self, text: str | None) -> None:
 917        if text is None:
 918            text = ""
 919        try:
 920            self.__element.text = text
 921        except TypeError as e:
 922            raise TypeError(f'Str type expected: "{type(text)}"') from e
 923
 924    @property
 925    def text_recursive(self) -> str:
 926        return "".join(str(x) for x in self.__element.itertext())
 927
 928    @property
 929    def tail(self) -> str | None:
 930        """Get / set the text immediately following the element."""
 931        return self.__element.tail
 932
 933    @tail.setter
 934    def tail(self, text: str | None) -> None:
 935        self.__element.tail = text or ""
 936
 937    def search(self, pattern: str) -> int | None:
 938        """Return the first position of the pattern in the text content of
 939        the element, or None if not found.
 940
 941        Python regular expression syntax applies.
 942
 943        Arguments:
 944
 945            pattern -- str
 946
 947        Return: int or None
 948        """
 949        match = re.search(pattern, self.text_recursive)
 950        if match is None:
 951            return None
 952        return match.start()
 953
 954    def match(self, pattern: str) -> bool:
 955        """return True if the pattern is found one or more times anywhere in
 956        the text content of the element.
 957
 958        Python regular expression syntax applies.
 959
 960        Arguments:
 961
 962            pattern -- str
 963
 964        Return: bool
 965        """
 966        return self.search(pattern) is not None
 967
 968    def replace(self, pattern: str, new: str | None = None) -> int:
 969        """Replace the pattern with the given text, or delete if text is an
 970        empty string, and return the number of replacements. By default, only
 971        return the number of occurences that would be replaced.
 972
 973        It cannot replace patterns found across several element, like a word
 974        split into two consecutive spans.
 975
 976        Python regular expression syntax applies.
 977
 978        Arguments:
 979
 980            pattern -- str
 981
 982            new -- str
 983
 984        Return: int
 985        """
 986        if not isinstance(pattern, str):
 987            # Fail properly if the pattern is an non-ascii bytestring
 988            pattern = str(pattern)
 989        cpattern = re.compile(pattern)
 990        count = 0
 991        for text in self.xpath("descendant::text()"):
 992            if new is None:
 993                count += len(cpattern.findall(str(text)))
 994            else:
 995                new_text, number = cpattern.subn(new, str(text))
 996                container = text.parent
 997                if text.is_text():  # type: ignore
 998                    container.text = new_text  # type: ignore
 999                else:
1000                    container.tail = new_text  # type: ignore
1001                count += number
1002        return count
1003
1004    @property
1005    def root(self) -> Element:
1006        element = self.__element
1007        tree = element.getroottree()
1008        root = tree.getroot()
1009        return Element.from_tag(root)
1010
1011    @property
1012    def parent(self) -> Element | None:
1013        element = self.__element
1014        parent = element.getparent()
1015        if parent is None:
1016            # Already at root
1017            return None
1018        return Element.from_tag(parent)
1019
1020    @property
1021    def is_bound(self) -> bool:
1022        return self.parent is not None
1023
1024    # def get_next_sibling(self):
1025    #     element = self.__element
1026    #     next_one = element.getnext()
1027    #     if next_one is None:
1028    #         return None
1029    #     return Element.from_tag(next_one)
1030    #
1031    # def get_prev_sibling(self):
1032    #     element = self.__element
1033    #     prev = element.getprevious()
1034    #     if prev is None:
1035    #         return None
1036    #     return Element.from_tag(prev)
1037
1038    @property
1039    def children(self) -> list[Element]:
1040        element = self.__element
1041        return [
1042            Element.from_tag(e)
1043            for e in element.iterchildren()
1044            if isinstance(e, _Element)
1045        ]
1046
1047    def index(self, child: Element) -> int:
1048        """Return the position of the child in this element.
1049
1050        Inspired by lxml
1051        """
1052        return self.__element.index(child.__element)
1053
1054    @property
1055    def text_content(self) -> str:
1056        """Get / set the text of the embedded paragraph, including embeded
1057        annotations, cells...
1058
1059        Set create a paragraph if missing
1060        """
1061        return "\n".join(
1062            child.text_recursive for child in self.get_elements("descendant::text:p")
1063        )
1064
1065    @text_content.setter
1066    def text_content(self, text: str | None) -> None:
1067        paragraphs = self.get_elements("text:p")
1068        if not paragraphs:
1069            # E.g., text:p in draw:text-box in draw:frame
1070            paragraphs = self.get_elements("*/text:p")
1071        if paragraphs:
1072            paragraph = paragraphs.pop(0)
1073            for obsolete in paragraphs:
1074                obsolete.delete()
1075        else:
1076            paragraph = Element.from_tag("text:p")
1077            self.insert(paragraph, FIRST_CHILD)
1078        # As "text_content" returned all text nodes, "text_content"
1079        # will overwrite all text nodes and children that may contain them
1080        element = paragraph.__element
1081        # Clear but the attributes
1082        del element[:]
1083        element.text = text
1084
1085    def _erase_text_content(self) -> None:
1086        paragraphs = self.get_elements("text:p")
1087        if not paragraphs:
1088            # E.g., text:p in draw:text-box in draw:frame
1089            paragraphs = self.get_elements("*/text:p")
1090        if paragraphs:
1091            paragraphs.pop(0)
1092            for obsolete in paragraphs:
1093                obsolete.delete()
1094
1095    def is_empty(self) -> bool:
1096        """Check if the element is empty : no text, no children, no tail.
1097
1098        Return: Boolean
1099        """
1100        element = self.__element
1101        if element.tail is not None:
1102            return False
1103        if element.text is not None:
1104            return False
1105        if list(element.iterchildren()):
1106            return False
1107        return True
1108
1109    def _get_successor(self, target: Element) -> tuple[Element | None, Element | None]:
1110        element = self.__element
1111        next_one = element.getnext()
1112        if next_one is not None:
1113            return Element.from_tag(next_one), target
1114        parent = self.parent
1115        if parent is None:
1116            return None, None
1117        return parent._get_successor(target.parent)  # type:ignore
1118
1119    def _get_between_base(  # noqa:C901
1120        self,
1121        tag1: Element,
1122        tag2: Element,
1123    ) -> list[Element]:
1124        def find_any_id(elem: Element) -> tuple[str, str, str]:
1125            elem_tag = elem.tag
1126            for attribute in (
1127                "text:id",
1128                "text:change-id",
1129                "text:name",
1130                "office:name",
1131                "text:ref-name",
1132                "xml:id",
1133            ):
1134                idx = elem.get_attribute(attribute)
1135                if idx is not None:
1136                    return elem_tag, attribute, str(idx)
1137            raise ValueError(f"No Id found in {elem.serialize()}")
1138
1139        def common_ancestor(
1140            tag1: str,
1141            attr1: str,
1142            val1: str,
1143            tag2: str,
1144            attr2: str,
1145            val2: str,
1146        ) -> Element | None:
1147            root = self.root
1148            request1 = f'descendant::{tag1}[@{attr1}="{val1}"]'
1149            request2 = f'descendant::{tag2}[@{attr2}="{val2}"]'
1150            ancestor = root.xpath(request1)[0]
1151            if ancestor is None:
1152                return None
1153            while True:
1154                # print "up",
1155                new_ancestor = ancestor.parent
1156                if new_ancestor is None:
1157                    return None
1158                has_tag2 = new_ancestor.xpath(request2)
1159                ancestor = new_ancestor
1160                if not has_tag2:
1161                    continue
1162                # print 'found'
1163                break
1164            # print up.serialize()
1165            return ancestor
1166
1167        elem1_tag, elem1_attr, elem1_val = find_any_id(tag1)
1168        elem2_tag, elem2_attr, elem2_val = find_any_id(tag2)
1169        ancestor_result = common_ancestor(
1170            elem1_tag,
1171            elem1_attr,
1172            elem1_val,
1173            elem2_tag,
1174            elem2_attr,
1175            elem2_val,
1176        )
1177        if ancestor_result is None:
1178            raise RuntimeError(f"No common ancestor for {elem1_tag} {elem2_tag}")
1179        ancestor = ancestor_result.clone
1180        path1 = f'{elem1_tag}[@{elem1_attr}="{elem1_val}"]'
1181        path2 = f'{elem2_tag}[@{elem2_attr}="{elem2_val}"]'
1182        result = ancestor.clone
1183        for child in result.children:
1184            result.delete(child)
1185        result.text = ""
1186        result.tail = ""
1187        target = result
1188        current = ancestor.children[0]
1189
1190        state = 0
1191        while True:
1192            if current is None:
1193                raise RuntimeError(f"No current ancestor for {elem1_tag} {elem2_tag}")
1194            # print 'current', state, current.serialize()
1195            if state == 0:  # before tag 1
1196                if current.xpath(f"descendant-or-self::{path1}"):
1197                    if current.xpath(f"self::{path1}"):
1198                        tail = current.tail
1199                        if tail:
1200                            # got a tail => the parent should be either t:p or t:h
1201                            target.text = tail  # type: ignore
1202                        current, target = current._get_successor(target)  # type: ignore
1203                        state = 1
1204                        continue
1205                    # got T1 in chidren, need further analysis
1206                    new_target = current.clone
1207                    for child in new_target.children:
1208                        new_target.delete(child)
1209                    new_target.text = ""
1210                    new_target.tail = ""
1211                    target.append(new_target)  # type: ignore
1212                    target = new_target
1213                    current = current.children[0]
1214                    continue
1215                else:
1216                    # before tag1 : forget element, go to next one
1217                    current, target = current._get_successor(target)  # type: ignore
1218                    continue
1219            elif state == 1:  # collect elements
1220                further = False
1221                if current.xpath(f"descendant-or-self::{path2}"):
1222                    if current.xpath(f"self::{path2}"):
1223                        # end of trip
1224                        break
1225                    # got T2 in chidren, need further analysis
1226                    further = True
1227                # further analysis needed :
1228                if further:
1229                    new_target = current.clone
1230                    for child in new_target.children:
1231                        new_target.delete(child)
1232                    new_target.text = ""
1233                    new_target.tail = ""
1234                    target.append(new_target)  # type: ignore
1235                    target = new_target
1236                    current = current.children[0]
1237                    continue
1238                # collect
1239                target.append(current.clone)  # type: ignore
1240                current, target = current._get_successor(target)  # type: ignore
1241                continue
1242        # Now resu should be the "parent" of inserted parts
1243        # - a text:h or text:p sigle item (simple case)
1244        # - a upper element, with some text:p, text:h in it => need to be
1245        #   stripped to have a list of text:p, text:h
1246        if result.tag in {"text:p", "text:h"}:
1247            inner = [result]
1248        else:
1249            inner = result.children
1250        return inner
1251
1252    def get_between(
1253        self,
1254        tag1: Element,
1255        tag2: Element,
1256        as_text: bool = False,
1257        clean: bool = True,
1258        no_header: bool = True,
1259    ) -> list | Element | str:
1260        """Returns elements between tag1 and tag2, tag1 and tag2 shall
1261        be unique and having an id attribute.
1262        (WARN: buggy if tag1/tag2 defines a malformed odf xml.)
1263        If as_text is True: returns the text content.
1264        If clean is True: suppress unwanted tags (deletions marks, ...)
1265        If no_header is True: existing text:h are changed in text:p
1266        By default: returns a list of Element, cleaned and without headers.
1267
1268        Implementation and standard retrictions:
1269        Only text:h and text:p sould be 'cut' by an insert tag, so inner parts
1270        of insert tags are:
1271
1272            - any text:h, text:p or sub tag of these
1273
1274            - some text, part of a parent text:h or text:p
1275
1276        Arguments:
1277
1278            tag1 -- Element
1279
1280            tag2 -- Element
1281
1282            as_text -- boolean
1283
1284            clean -- boolean
1285
1286            no_header -- boolean
1287
1288        Return: list of odf_paragraph or odf_header
1289        """
1290        inner = self._get_between_base(tag1, tag2)
1291
1292        if clean:
1293            clean_tags = (
1294                "text:change",
1295                "text:change-start",
1296                "text:change-end",
1297                "text:reference-mark",
1298                "text:reference-mark-start",
1299                "text:reference-mark-end",
1300            )
1301            request_self = " | ".join(["self::%s" % c for c in clean_tags])
1302            inner = [e for e in inner if not e.xpath(request_self)]
1303            request = " | ".join([f"descendant::{tag}" for tag in clean_tags])
1304            for element in inner:
1305                to_del = element.xpath(request)
1306                for elem in to_del:
1307                    if isinstance(elem, Element):
1308                        element.delete(elem)
1309        if no_header:  # crude replace t:h by t:p
1310            new_inner = []
1311            for element in inner:
1312                if element.tag == "text:h":
1313                    children = element.children
1314                    text = element.__element.text
1315                    para = Element.from_tag("text:p")
1316                    para.text = text or ""
1317                    for c in children:
1318                        para.append(c)
1319                    new_inner.append(para)
1320                else:
1321                    new_inner.append(element)
1322            inner = new_inner
1323        if as_text:
1324            return "\n".join([e.get_formatted_text() for e in inner])
1325        else:
1326            return inner
1327
1328    def insert(
1329        self,
1330        element: Element,
1331        xmlposition: int | None = None,
1332        position: int | None = None,
1333        start: bool = False,
1334    ) -> None:
1335        """Insert an element relatively to ourself.
1336
1337        Insert either using DOM vocabulary or by numeric position.
1338        If text start is True, insert the element before any existing text.
1339
1340        Position start at 0.
1341
1342        Arguments:
1343
1344            element -- Element
1345
1346            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
1347                           or PREV_SIBLING
1348
1349            start -- Boolean
1350
1351            position -- int
1352        """
1353        # child_tag = element.tag
1354        current = self.__element
1355        _element = element.__element
1356        if start:
1357            text = current.text
1358            if text is not None:
1359                current.text = None
1360                tail = _element.tail
1361                if tail is None:
1362                    tail = text
1363                else:
1364                    tail = tail + text
1365                _element.tail = tail
1366            position = 0
1367        if position is not None:
1368            current.insert(position, _element)
1369        elif xmlposition is FIRST_CHILD:
1370            current.insert(0, _element)
1371        elif xmlposition is LAST_CHILD:
1372            current.append(_element)
1373        elif xmlposition is NEXT_SIBLING:
1374            parent = current.getparent()
1375            index = parent.index(current)  # type: ignore
1376            parent.insert(index + 1, _element)  # type: ignore
1377        elif xmlposition is PREV_SIBLING:
1378            parent = current.getparent()
1379            index = parent.index(current)  # type: ignore
1380            parent.insert(index, _element)  # type: ignore
1381        else:
1382            raise ValueError("(xml)position must be defined")
1383
1384    def extend(self, odf_elements: Iterable[Element]) -> None:
1385        """Fast append elements at the end of ourself using extend."""
1386        if odf_elements:
1387            current = self.__element
1388            elements = [element.__element for element in odf_elements]
1389            current.extend(elements)
1390
1391    def append(self, str_or_element: str | Element) -> None:
1392        """Insert element or text in the last position."""
1393        current = self.__element
1394        if isinstance(str_or_element, str):
1395            # Has children ?
1396            children = list(current.iterchildren())
1397            if children:
1398                # Append to tail of the last child
1399                last_child = children[-1]
1400                text = last_child.tail
1401                text = text if text is not None else ""
1402                text += str_or_element
1403                last_child.tail = text
1404            else:
1405                # Append to text of the element
1406                text = current.text
1407                text = text if text is not None else ""
1408                text += str_or_element
1409                current.text = text
1410        elif isinstance(str_or_element, Element):
1411            current.append(str_or_element.__element)
1412        else:
1413            raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')
1414
1415    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
1416        """Delete the given element from the XML tree. If no element is given,
1417        "self" is deleted. The XML library may allow to continue to use an
1418        element now "orphan" as long as you have a reference to it.
1419
1420        if keep_tail is True (default), the tail text is not erased.
1421
1422        Arguments:
1423
1424            child -- Element
1425
1426            keep_tail -- boolean (default to True), True for most usages.
1427        """
1428        if child is None:
1429            parent = self.parent
1430            if parent is None:
1431                raise ValueError(f"Can't delete the root element\n{self.serialize()}")
1432            child = self
1433        else:
1434            parent = self
1435        if keep_tail and child.__element.tail is not None:
1436            current = child.__element
1437            tail = str(current.tail)
1438            current.tail = None
1439            prev = current.getprevious()
1440            if prev is not None:
1441                if prev.tail is None:
1442                    prev.tail = tail
1443                else:
1444                    prev.tail += tail
1445            else:
1446                if parent.__element.text is None:
1447                    parent.__element.text = tail
1448                else:
1449                    parent.__element.text += tail
1450        parent.__element.remove(child.__element)
1451
1452    def replace_element(self, old_element: Element, new_element: Element) -> None:
1453        """Replaces in place a sub element with the element passed as second
1454        argument.
1455
1456        Warning : no clone for old element.
1457        """
1458        current = self.__element
1459        current.replace(old_element.__element, new_element.__element)
1460
1461    def strip_elements(
1462        self,
1463        sub_elements: Element | Iterable[Element],
1464    ) -> Element | list:
1465        """Remove the tags of provided elements, keeping inner childs and text.
1466
1467        Return : the striped element.
1468
1469        Warning : no clone in sub_elements list.
1470
1471        Arguments:
1472
1473            sub_elements -- Element or list of Element
1474        """
1475        if not sub_elements:
1476            return self
1477        if isinstance(sub_elements, Element):
1478            sub_elements = (sub_elements,)
1479        replacer = _get_lxml_tag("text:this-will-be-removed")
1480        for element in sub_elements:
1481            element.__element.tag = replacer
1482        strip = ("text:this-will-be-removed",)
1483        return self.strip_tags(strip=strip, default=None)
1484
1485    def strip_tags(
1486        self,
1487        strip: Iterable[str] | None = None,
1488        protect: Iterable[str] | None = None,
1489        default: str | None = "text:p",
1490    ) -> Element | list:
1491        """Remove the tags listed in strip, recursively, keeping inner childs
1492        and text. Tags listed in protect stop the removal one level depth. If
1493        the first level element is stripped, default is used to embed the
1494        content in the default element. If default is None and first level is
1495        striped, a list of text and children is returned. Return : the striped
1496        element.
1497
1498        strip_tags should be used by on purpose methods (strip_span ...)
1499        (Method name taken from lxml).
1500
1501        Arguments:
1502
1503            strip -- iterable list of str odf tags, or None
1504
1505            protect -- iterable list of str odf tags, or None
1506
1507            default -- str odf tag, or None
1508
1509        Return:
1510
1511            Element.
1512        """
1513        if not strip:
1514            return self
1515        if not protect:
1516            protect = ()
1517        protected = False
1518        element, modified = Element._strip_tags(self, strip, protect, protected)
1519        if modified and isinstance(element, list) and default:
1520            new = Element.from_tag(default)
1521            for content in element:
1522                if isinstance(content, Element):
1523                    new.append(content)
1524                else:
1525                    new.text = content
1526            element = new
1527        return element
1528
1529    @staticmethod
1530    def _strip_tags(  # noqa:C901
1531        element: Element,
1532        strip: Iterable[str],
1533        protect: Iterable[str],
1534        protected: bool,
1535    ) -> tuple[Element | list, bool]:
1536        """Sub method for strip_tags()."""
1537        element_clone = element.clone
1538        modified = False
1539        children = []
1540        if protect and element.tag in protect:
1541            protect_below = True
1542        else:
1543            protect_below = False
1544        for child in element_clone.children:
1545            striped_child, is_modified = Element._strip_tags(
1546                child, strip, protect, protect_below
1547            )
1548            if is_modified:
1549                modified = True
1550            if isinstance(striped_child, list):
1551                children.extend(striped_child)
1552            else:
1553                children.append(striped_child)
1554
1555        text = element_clone.text
1556        tail = element_clone.tail
1557        if not protected and strip and element.tag in strip:
1558            element_result: list[Element | str] = []
1559            if text is not None:
1560                element_result.append(text)
1561            for child in children:
1562                element_result.append(child)
1563            if tail is not None:
1564                element_result.append(tail)
1565            return (element_result, True)
1566        else:
1567            if not modified:
1568                return (element, False)
1569            element.clear()
1570            try:
1571                for key, value in element_clone.attributes.items():
1572                    element.set_attribute(key, value)
1573            except ValueError:
1574                sys.stderr.write(f"strip_tags(): bad attribute in {element_clone}\n")
1575            if text is not None:
1576                element.append(text)
1577            for child in children:
1578                element.append(child)
1579            if tail is not None:
1580                element.tail = tail
1581            return (element, True)
1582
1583    def xpath(self, xpath_query: str) -> list[Element | Text]:
1584        """Apply XPath query to the element and its subtree. Return list of
1585        Element or Text instances translated from the nodes found.
1586        """
1587        element = self.__element
1588        xpath_instance = xpath_compile(xpath_query)
1589        elements = xpath_instance(element)
1590        result: list[Element | Text] = []
1591        if hasattr(elements, "__iter__"):
1592            for obj in elements:  # type: ignore
1593                if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)):
1594                    result.append(Text(obj))
1595                elif isinstance(obj, _Element):
1596                    result.append(Element.from_tag(obj))
1597                # else:
1598                #     result.append(obj)
1599        return result
1600
1601    def clear(self) -> None:
1602        """Remove text, children and attributes from the element."""
1603        self.__element.clear()
1604        if hasattr(self, "_tmap"):
1605            self._tmap: list[int] = []
1606        if hasattr(self, "_cmap"):
1607            self._cmap: list[int] = []
1608        if hasattr(self, "_rmap"):
1609            self._rmap: list[int] = []
1610        if hasattr(self, "_indexes"):
1611            remember = False
1612            if "_rmap" in self._indexes:
1613                remember = True
1614            self._indexes: dict[str, dict] = {}
1615            self._indexes["_cmap"] = {}
1616            self._indexes["_tmap"] = {}
1617            if remember:
1618                self._indexes["_rmap"] = {}
1619
1620    @property
1621    def clone(self) -> Element:
1622        clone = deepcopy(self.__element)
1623        root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES)
1624        root.append(clone)
1625        return self.from_tag(clone)
1626
1627        # slow data = tostring(self.__element, encoding='unicode')
1628        # return self.from_tag(data)
1629
1630    @staticmethod
1631    def _strip_namespaces(data: str) -> str:
1632        """Remove xmlns:* fields from serialized XML."""
1633        return re.sub(r' xmlns:\w*="[\w:\-\/\.#]*"', "", data)
1634
1635    def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1636        """Return text serialization of XML element."""
1637        # This copy bypasses serialization side-effects in lxml
1638        native = deepcopy(self.__element)
1639        data = tostring(
1640            native, with_tail=False, pretty_print=pretty, encoding="unicode"
1641        )
1642        if with_ns:
1643            return data
1644        # Remove namespaces
1645        return self._strip_namespaces(data)
1646
1647    # Element helpers usable from any context
1648
1649    @property
1650    def document_body(self) -> Element | None:
1651        """Return the document body : 'office:body'"""
1652        return self.get_element("//office:body/*[1]")
1653
1654    @document_body.setter
1655    def document_body(self, new_body: Element) -> None:
1656        """Change in place the full document body content."""
1657        body = self.document_body
1658        if body is None:
1659            raise ValueError("//office:body not found in document")
1660        tail = body.tail
1661        body.clear()
1662        for item in new_body.children:
1663            body.append(item)
1664        if tail:
1665            body.tail = tail
1666
1667    def get_formatted_text(self, context: dict | None = None) -> str:
1668        """This function should return a beautiful version of the text."""
1669        return ""
1670
1671    def get_styled_elements(self, name: str = "") -> list[Element]:
1672        """Brute-force to find paragraphs, tables, etc. using the given style
1673        name (or all by default).
1674
1675        Arguments:
1676
1677            name -- str
1678
1679        Return: list
1680        """
1681        # FIXME incomplete (and possibly inaccurate)
1682        return (
1683            self._filtered_elements("descendant::*", text_style=name)
1684            + self._filtered_elements("descendant::*", draw_style=name)
1685            + self._filtered_elements("descendant::*", draw_text_style=name)
1686            + self._filtered_elements("descendant::*", table_style=name)
1687            + self._filtered_elements("descendant::*", page_layout=name)
1688            + self._filtered_elements("descendant::*", master_page=name)
1689            + self._filtered_elements("descendant::*", parent_style=name)
1690        )
1691
1692    # Common attributes
1693
1694    def _get_inner_text(self, tag: str) -> str | None:
1695        element = self.get_element(tag)
1696        if element is None:
1697            return None
1698        return element.text
1699
1700    def _set_inner_text(self, tag: str, text: str) -> None:
1701        element = self.get_element(tag)
1702        if element is None:
1703            element = Element.from_tag(tag)
1704            self.append(element)
1705        element.text = text
1706
1707    # Dublin core
1708
1709    @property
1710    def dc_creator(self) -> str | None:
1711        """Get dc:creator value.
1712
1713        Return: str (or None if inexistant)
1714        """
1715        return self._get_inner_text("dc:creator")
1716
1717    @dc_creator.setter
1718    def dc_creator(self, creator: str) -> None:
1719        """Set dc:creator value.
1720
1721        Arguments:
1722
1723            creator -- str
1724        """
1725        self._set_inner_text("dc:creator", creator)
1726
1727    @property
1728    def dc_date(self) -> datetime | None:
1729        """Get the dc:date value.
1730
1731        Return: datetime (or None if inexistant)
1732        """
1733        date = self._get_inner_text("dc:date")
1734        if date is None:
1735            return None
1736        return DateTime.decode(date)
1737
1738    @dc_date.setter
1739    def dc_date(self, date: datetime) -> None:
1740        """Set the dc:date value.
1741
1742        Arguments:
1743
1744            darz -- datetime
1745        """
1746        self._set_inner_text("dc:date", DateTime.encode(date))
1747
1748    # SVG
1749
1750    @property
1751    def svg_title(self) -> str | None:
1752        return self._get_inner_text("svg:title")
1753
1754    @svg_title.setter
1755    def svg_title(self, title: str) -> None:
1756        self._set_inner_text("svg:title", title)
1757
1758    @property
1759    def svg_description(self) -> str | None:
1760        return self._get_inner_text("svg:desc")
1761
1762    @svg_description.setter
1763    def svg_description(self, description: str) -> None:
1764        self._set_inner_text("svg:desc", description)
1765
1766    # Sections
1767
1768    def get_sections(
1769        self,
1770        style: str | None = None,
1771        content: str | None = None,
1772    ) -> list[Element]:
1773        """Return all the sections that match the criteria.
1774
1775        Arguments:
1776
1777            style -- str
1778
1779            content -- str regex
1780
1781        Return: list of Element
1782        """
1783        return self._filtered_elements(
1784            "text:section", text_style=style, content=content
1785        )
1786
1787    def get_section(
1788        self,
1789        position: int = 0,
1790        content: str | None = None,
1791    ) -> Element | None:
1792        """Return the section that matches the criteria.
1793
1794        Arguments:
1795
1796            position -- int
1797
1798            content -- str regex
1799
1800        Return: Element or None if not found
1801        """
1802        return self._filtered_element(
1803            "descendant::text:section", position, content=content
1804        )
1805
1806    # Paragraphs
1807
1808    def get_paragraphs(
1809        self,
1810        style: str | None = None,
1811        content: str | None = None,
1812    ) -> list[Element]:
1813        """Return all the paragraphs that match the criteria.
1814
1815        Arguments:
1816
1817            style -- str
1818
1819            content -- str regex
1820
1821        Return: list of Paragraph
1822        """
1823        return self._filtered_elements(
1824            "descendant::text:p", text_style=style, content=content
1825        )
1826
1827    def get_paragraph(
1828        self,
1829        position: int = 0,
1830        content: str | None = None,
1831    ) -> Element | None:
1832        """Return the paragraph that matches the criteria.
1833
1834        Arguments:
1835
1836            position -- int
1837
1838            content -- str regex
1839
1840        Return: Paragraph or None if not found
1841        """
1842        return self._filtered_element("descendant::text:p", position, content=content)
1843
1844    # Span
1845
1846    def get_spans(
1847        self,
1848        style: str | None = None,
1849        content: str | None = None,
1850    ) -> list[Element]:
1851        """Return all the spans that match the criteria.
1852
1853        Arguments:
1854
1855            style -- str
1856
1857            content -- str regex
1858
1859        Return: list of Span
1860        """
1861        return self._filtered_elements(
1862            "descendant::text:span", text_style=style, content=content
1863        )
1864
1865    def get_span(
1866        self,
1867        position: int = 0,
1868        content: str | None = None,
1869    ) -> Element | None:
1870        """Return the span that matches the criteria.
1871
1872        Arguments:
1873
1874            position -- int
1875
1876            content -- str regex
1877
1878        Return: Span or None if not found
1879        """
1880        return self._filtered_element(
1881            "descendant::text:span", position, content=content
1882        )
1883
1884    # Headers
1885
1886    def get_headers(
1887        self,
1888        style: str | None = None,
1889        outline_level: str | None = None,
1890        content: str | None = None,
1891    ) -> list[Element]:
1892        """Return all the Headers that match the criteria.
1893
1894        Arguments:
1895
1896            style -- str
1897
1898            content -- str regex
1899
1900        Return: list of Header
1901        """
1902        return self._filtered_elements(
1903            "descendant::text:h",
1904            text_style=style,
1905            outline_level=outline_level,
1906            content=content,
1907        )
1908
1909    def get_header(
1910        self,
1911        position: int = 0,
1912        outline_level: str | None = None,
1913        content: str | None = None,
1914    ) -> Element | None:
1915        """Return the Header that matches the criteria.
1916
1917        Arguments:
1918
1919            position -- int
1920
1921            content -- str regex
1922
1923        Return: Header or None if not found
1924        """
1925        return self._filtered_element(
1926            "descendant::text:h",
1927            position,
1928            outline_level=outline_level,
1929            content=content,
1930        )
1931
1932    # Lists
1933
1934    def get_lists(
1935        self,
1936        style: str | None = None,
1937        content: str | None = None,
1938    ) -> list[Element]:
1939        """Return all the lists that match the criteria.
1940
1941        Arguments:
1942
1943            style -- str
1944
1945            content -- str regex
1946
1947        Return: list of List
1948        """
1949        return self._filtered_elements(
1950            "descendant::text:list", text_style=style, content=content
1951        )
1952
1953    def get_list(
1954        self,
1955        position: int = 0,
1956        content: str | None = None,
1957    ) -> Element | None:
1958        """Return the list that matches the criteria.
1959
1960        Arguments:
1961
1962            position -- int
1963
1964            content -- str regex
1965
1966        Return: List or None if not found
1967        """
1968        return self._filtered_element(
1969            "descendant::text:list", position, content=content
1970        )
1971
1972    # Frames
1973
1974    def get_frames(
1975        self,
1976        presentation_class: str | None = None,
1977        style: str | None = None,
1978        title: str | None = None,
1979        description: str | None = None,
1980        content: str | None = None,
1981    ) -> list[Element]:
1982        """Return all the frames that match the criteria.
1983
1984        Arguments:
1985
1986            presentation_class -- str
1987
1988            style -- str
1989
1990            title -- str regex
1991
1992            description -- str regex
1993
1994            content -- str regex
1995
1996        Return: list of Frame
1997        """
1998        return self._filtered_elements(
1999            "descendant::draw:frame",
2000            presentation_class=presentation_class,
2001            draw_style=style,
2002            svg_title=title,
2003            svg_desc=description,
2004            content=content,
2005        )
2006
2007    def get_frame(
2008        self,
2009        position: int = 0,
2010        name: str | None = None,
2011        presentation_class: str | None = None,
2012        title: str | None = None,
2013        description: str | None = None,
2014        content: str | None = None,
2015    ) -> Element | None:
2016        """Return the section that matches the criteria.
2017
2018        Arguments:
2019
2020            position -- int
2021
2022            name -- str
2023
2024            presentation_class -- str
2025
2026            title -- str regex
2027
2028            description -- str regex
2029
2030            content -- str regex
2031
2032        Return: Frame or None if not found
2033        """
2034        return self._filtered_element(
2035            "descendant::draw:frame",
2036            position,
2037            draw_name=name,
2038            presentation_class=presentation_class,
2039            svg_title=title,
2040            svg_desc=description,
2041            content=content,
2042        )
2043
2044    # Images
2045
2046    def get_images(
2047        self,
2048        style: str | None = None,
2049        url: str | None = None,
2050        content: str | None = None,
2051    ) -> list[Element]:
2052        """Return all the images matching the criteria.
2053
2054        Arguments:
2055
2056            style -- str
2057
2058            url -- str regex
2059
2060            content -- str regex
2061
2062        Return: list of Element
2063        """
2064        return self._filtered_elements(
2065            "descendant::draw:image", text_style=style, url=url, content=content
2066        )
2067
2068    def get_image(
2069        self,
2070        position: int = 0,
2071        name: str | None = None,
2072        url: str | None = None,
2073        content: str | None = None,
2074    ) -> Element | None:
2075        """Return the image matching the criteria.
2076
2077        Arguments:
2078
2079            position -- int
2080
2081            name -- str
2082
2083            url -- str regex
2084
2085            content -- str regex
2086
2087        Return: Element or None if not found
2088        """
2089        # The frame is holding the name
2090        if name is not None:
2091            frame = self._filtered_element(
2092                "descendant::draw:frame", position, draw_name=name
2093            )
2094            if frame is None:
2095                return None
2096            # The name is supposedly unique
2097            return frame.get_element("draw:image")
2098        return self._filtered_element(
2099            "descendant::draw:image", position, url=url, content=content
2100        )
2101
2102    # Tables
2103
2104    def get_tables(
2105        self,
2106        style: str | None = None,
2107        content: str | None = None,
2108    ) -> list[Element]:
2109        """Return all the tables that match the criteria.
2110
2111        Arguments:
2112
2113            style -- str
2114
2115            content -- str regex
2116
2117        Return: list of Table
2118        """
2119        return self._filtered_elements(
2120            "descendant::table:table", table_style=style, content=content
2121        )
2122
2123    def get_table(
2124        self,
2125        position: int = 0,
2126        name: str | None = None,
2127        content: str | None = None,
2128    ) -> Element | None:
2129        """Return the table that matches the criteria.
2130
2131        Arguments:
2132
2133            position -- int
2134
2135            name -- str
2136
2137            content -- str regex
2138
2139        Return: Table or None if not found
2140        """
2141        if name is None and content is None:
2142            result = self._filtered_element("descendant::table:table", position)
2143        else:
2144            result = self._filtered_element(
2145                "descendant::table:table",
2146                position,
2147                table_name=name,
2148                content=content,
2149            )
2150        return result
2151
2152    # Named Range
2153
2154    def get_named_ranges(self) -> list[Element]:
2155        """Return all the tables named ranges.
2156
2157        Return: list of odf_named_range
2158        """
2159        named_ranges = self.get_elements(
2160            "descendant::table:named-expressions/table:named-range"
2161        )
2162        return named_ranges
2163
2164    def get_named_range(self, name: str) -> Element | None:
2165        """Return the named range of specified name, or None if not found.
2166
2167        Arguments:
2168
2169            name -- str
2170
2171        Return: NamedRange
2172        """
2173        named_range = self.get_elements(
2174            f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]'
2175        )
2176        if named_range:
2177            return named_range[0]
2178        else:
2179            return None
2180
2181    def append_named_range(self, named_range: Element) -> None:
2182        """Append the named range to the spreadsheet, replacing existing named
2183        range of same name if any.
2184
2185        Arguments:
2186
2187            named_range --  NamedRange
2188        """
2189        if self.tag != "office:spreadsheet":
2190            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2191        named_expressions = self.get_element("table:named-expressions")
2192        if not named_expressions:
2193            named_expressions = Element.from_tag("table:named-expressions")
2194            self.append(named_expressions)
2195        # exists ?
2196        current = named_expressions.get_element(
2197            f'table:named-range[@table:name="{named_range.name}"][1]'  # type:ignore
2198        )
2199        if current:
2200            named_expressions.delete(current)
2201        named_expressions.append(named_range)
2202
2203    def delete_named_range(self, name: str) -> None:
2204        """Delete the Named Range of specified name from the spreadsheet.
2205
2206        Arguments:
2207
2208            name -- str
2209        """
2210        if self.tag != "office:spreadsheet":
2211            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2212        named_range = self.get_named_range(name)
2213        if not named_range:
2214            return
2215        named_range.delete()
2216        named_expressions = self.get_element("table:named-expressions")
2217        if not named_expressions:
2218            return
2219        element = named_expressions.__element
2220        children = list(element.iterchildren())
2221        if not children:
2222            self.delete(named_expressions)
2223
2224    # Notes
2225
2226    def get_notes(
2227        self,
2228        note_class: str | None = None,
2229        content: str | None = None,
2230    ) -> list[Element]:
2231        """Return all the notes that match the criteria.
2232
2233        Arguments:
2234
2235            note_class -- 'footnote' or 'endnote'
2236
2237            content -- str regex
2238
2239        Return: list of Note
2240        """
2241        return self._filtered_elements(
2242            "descendant::text:note", note_class=note_class, content=content
2243        )
2244
2245    def get_note(
2246        self,
2247        position: int = 0,
2248        note_id: str | None = None,
2249        note_class: str | None = None,
2250        content: str | None = None,
2251    ) -> Element | None:
2252        """Return the note that matches the criteria.
2253
2254        Arguments:
2255
2256            position -- int
2257
2258            note_id -- str
2259
2260            note_class -- 'footnote' or 'endnote'
2261
2262            content -- str regex
2263
2264        Return: Note or None if not found
2265        """
2266        return self._filtered_element(
2267            "descendant::text:note",
2268            position,
2269            text_id=note_id,
2270            note_class=note_class,
2271            content=content,
2272        )
2273
2274    # Annotations
2275
2276    def get_annotations(
2277        self,
2278        creator: str | None = None,
2279        start_date: datetime | None = None,
2280        end_date: datetime | None = None,
2281        content: str | None = None,
2282    ) -> list[Element]:
2283        """Return all the annotations that match the criteria.
2284
2285        Arguments:
2286
2287            creator -- str
2288
2289            start_date -- datetime instance
2290
2291            end_date --  datetime instance
2292
2293            content -- str regex
2294
2295        Return: list of Annotation
2296        """
2297        annotations = []
2298        for annotation in self._filtered_elements(
2299            "descendant::office:annotation", content=content
2300        ):
2301            if creator is not None and creator != annotation.dc_creator:
2302                continue
2303            date = annotation.dc_date
2304            if date is None:
2305                continue
2306            if start_date is not None and date < start_date:
2307                continue
2308            if end_date is not None and date >= end_date:
2309                continue
2310            annotations.append(annotation)
2311        return annotations
2312
2313    def get_annotation(
2314        self,
2315        position: int = 0,
2316        creator: str | None = None,
2317        start_date: datetime | None = None,
2318        end_date: datetime | None = None,
2319        content: str | None = None,
2320        name: str | None = None,
2321    ) -> Element | None:
2322        """Return the annotation that matches the criteria.
2323
2324        Arguments:
2325
2326            position -- int
2327
2328            creator -- str
2329
2330            start_date -- datetime instance
2331
2332            end_date -- datetime instance
2333
2334            content -- str regex
2335
2336            name -- str
2337
2338        Return: Annotation or None if not found
2339        """
2340        if name is not None:
2341            return self._filtered_element(
2342                "descendant::office:annotation", 0, office_name=name
2343            )
2344        annotations = self.get_annotations(
2345            creator=creator, start_date=start_date, end_date=end_date, content=content
2346        )
2347        if not annotations:
2348            return None
2349        try:
2350            return annotations[position]
2351        except IndexError:
2352            return None
2353
2354    def get_annotation_ends(self) -> list[Element]:
2355        """Return all the annotation ends.
2356
2357        Return: list of Element
2358        """
2359        return self._filtered_elements("descendant::office:annotation-end")
2360
2361    def get_annotation_end(
2362        self,
2363        position: int = 0,
2364        name: str | None = None,
2365    ) -> Element | None:
2366        """Return the annotation end that matches the criteria.
2367
2368        Arguments:
2369
2370            position -- int
2371
2372            name -- str
2373
2374        Return: Element or None if not found
2375        """
2376        return self._filtered_element(
2377            "descendant::office:annotation-end", position, office_name=name
2378        )
2379
2380    # office:names
2381
2382    def get_office_names(self) -> list[str]:
2383        """Return all the used office:name tags values of the element.
2384
2385        Return: list of unique str
2386        """
2387        name_xpath_query = xpath_compile("//@office:name")
2388        response = name_xpath_query(self.__element)
2389        if not isinstance(response, list):
2390            return []
2391        return list({str(name) for name in response if name})
2392
2393    # Variables
2394
2395    def get_variable_decls(self) -> Element:
2396        """Return the container for variable declarations. Created if not
2397        found.
2398
2399        Return: Element
2400        """
2401        variable_decls = self.get_element("//text:variable-decls")
2402        if variable_decls is None:
2403            body = self.document_body
2404            if not body:
2405                raise ValueError("Empty document.body")
2406            body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD)
2407            variable_decls = body.get_element("//text:variable-decls")
2408
2409        return variable_decls  # type:ignore
2410
2411    def get_variable_decl_list(self) -> list[Element]:
2412        """Return all the variable declarations.
2413
2414        Return: list of Element
2415        """
2416        return self._filtered_elements("descendant::text:variable-decl")
2417
2418    def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2419        """return the variable declaration for the given name.
2420
2421        Arguments:
2422
2423            name -- str
2424
2425            position -- int
2426
2427        return: Element or none if not found
2428        """
2429        return self._filtered_element(
2430            "descendant::text:variable-decl", position, text_name=name
2431        )
2432
2433    def get_variable_sets(self, name: str | None = None) -> list[Element]:
2434        """Return all the variable sets that match the criteria.
2435
2436        Arguments:
2437
2438            name -- str
2439
2440        Return: list of Element
2441        """
2442        return self._filtered_elements("descendant::text:variable-set", text_name=name)
2443
2444    def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2445        """Return the variable set for the given name (last one by default).
2446
2447        Arguments:
2448
2449            name -- str
2450
2451            position -- int
2452
2453        Return: Element or None if not found
2454        """
2455        return self._filtered_element(
2456            "descendant::text:variable-set", position, text_name=name
2457        )
2458
2459    def get_variable_set_value(
2460        self,
2461        name: str,
2462        value_type: str | None = None,
2463    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2464        """Return the last value of the given variable name.
2465
2466        Arguments:
2467
2468            name -- str
2469
2470            value_type -- 'boolean', 'currency', 'date', 'float',
2471                          'percentage', 'string', 'time' or automatic
2472
2473        Return: most appropriate Python type
2474        """
2475        variable_set = self.get_variable_set(name)
2476        if not variable_set:
2477            return None
2478        return variable_set.get_value(value_type)  # type: ignore
2479
2480    # User fields
2481
2482    def get_user_field_decls(self) -> Element | None:
2483        """Return the container for user field declarations. Created if not
2484        found.
2485
2486        Return: Element
2487        """
2488        user_field_decls = self.get_element("//text:user-field-decls")
2489        if user_field_decls is None:
2490            body = self.document_body
2491            if not body:
2492                raise ValueError("Empty document.body")
2493            body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD)
2494            user_field_decls = body.get_element("//text:user-field-decls")
2495
2496        return user_field_decls
2497
2498    def get_user_field_decl_list(self) -> list[Element]:
2499        """Return all the user field declarations.
2500
2501        Return: list of Element
2502        """
2503        return self._filtered_elements("descendant::text:user-field-decl")
2504
2505    def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2506        """return the user field declaration for the given name.
2507
2508        return: Element or none if not found
2509        """
2510        return self._filtered_element(
2511            "descendant::text:user-field-decl", position, text_name=name
2512        )
2513
2514    def get_user_field_value(
2515        self, name: str, value_type: str | None = None
2516    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2517        """Return the value of the given user field name.
2518
2519        Arguments:
2520
2521            name -- str
2522
2523            value_type -- 'boolean', 'currency', 'date', 'float',
2524                          'percentage', 'string', 'time' or automatic
2525
2526        Return: most appropriate Python type
2527        """
2528        user_field_decl = self.get_user_field_decl(name)
2529        if user_field_decl is None:
2530            return None
2531        return user_field_decl.get_value(value_type)  # type: ignore
2532
2533    # User defined fields
2534    # They are fields who should contain a copy of a user defined medtadata
2535
2536    def get_user_defined_list(self) -> list[Element]:
2537        """Return all the user defined field declarations.
2538
2539        Return: list of Element
2540        """
2541        return self._filtered_elements("descendant::text:user-defined")
2542
2543    def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2544        """return the user defined declaration for the given name.
2545
2546        return: Element or none if not found
2547        """
2548        return self._filtered_element(
2549            "descendant::text:user-defined", position, text_name=name
2550        )
2551
2552    def get_user_defined_value(
2553        self, name: str, value_type: str | None = None
2554    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2555        """Return the value of the given user defined field name.
2556
2557        Arguments:
2558
2559            name -- str
2560
2561            value_type -- 'boolean', 'date', 'float',
2562                          'string', 'time' or automatic
2563
2564        Return: most appropriate Python type
2565        """
2566        user_defined = self.get_user_defined(name)
2567        if user_defined is None:
2568            return None
2569        return user_defined.get_value(value_type)  # type: ignore
2570
2571    # Draw Pages
2572
2573    def get_draw_pages(
2574        self,
2575        style: str | None = None,
2576        content: str | None = None,
2577    ) -> list[Element]:
2578        """Return all the draw pages that match the criteria.
2579
2580        Arguments:
2581
2582            style -- str
2583
2584            content -- str regex
2585
2586        Return: list of DrawPage
2587        """
2588        return self._filtered_elements(
2589            "descendant::draw:page", draw_style=style, content=content
2590        )
2591
2592    def get_draw_page(
2593        self,
2594        position: int = 0,
2595        name: str | None = None,
2596        content: str | None = None,
2597    ) -> Element | None:
2598        """Return the draw page that matches the criteria.
2599
2600        Arguments:
2601
2602            position -- int
2603
2604            name -- str
2605
2606            content -- str regex
2607
2608        Return: DrawPage or None if not found
2609        """
2610        return self._filtered_element(
2611            "descendant::draw:page", position, draw_name=name, content=content
2612        )
2613
2614    # Links
2615
2616    def get_links(
2617        self,
2618        name: str | None = None,
2619        title: str | None = None,
2620        url: str | None = None,
2621        content: str | None = None,
2622    ) -> list[Element]:
2623        """Return all the links that match the criteria.
2624
2625        Arguments:
2626
2627            name -- str
2628
2629            title -- str
2630
2631            url -- str regex
2632
2633            content -- str regex
2634
2635        Return: list of Element
2636        """
2637        return self._filtered_elements(
2638            "descendant::text:a",
2639            office_name=name,
2640            office_title=title,
2641            url=url,
2642            content=content,
2643        )
2644
2645    def get_link(
2646        self,
2647        position: int = 0,
2648        name: str | None = None,
2649        title: str | None = None,
2650        url: str | None = None,
2651        content: str | None = None,
2652    ) -> Element | None:
2653        """Return the link that matches the criteria.
2654
2655        Arguments:
2656
2657            position -- int
2658
2659            name -- str
2660
2661            title -- str
2662
2663            url -- str regex
2664
2665            content -- str regex
2666
2667        Return: Element or None if not found
2668        """
2669        return self._filtered_element(
2670            "descendant::text:a",
2671            position,
2672            office_name=name,
2673            office_title=title,
2674            url=url,
2675            content=content,
2676        )
2677
2678    # Bookmarks
2679
2680    def get_bookmarks(self) -> list[Element]:
2681        """Return all the bookmarks.
2682
2683        Return: list of Element
2684        """
2685        return self._filtered_elements("descendant::text:bookmark")
2686
2687    def get_bookmark(
2688        self,
2689        position: int = 0,
2690        name: str | None = None,
2691    ) -> Element | None:
2692        """Return the bookmark that matches the criteria.
2693
2694        Arguments:
2695
2696            position -- int
2697
2698            name -- str
2699
2700        Return: Bookmark or None if not found
2701        """
2702        return self._filtered_element(
2703            "descendant::text:bookmark", position, text_name=name
2704        )
2705
2706    def get_bookmark_starts(self) -> list[Element]:
2707        """Return all the bookmark starts.
2708
2709        Return: list of Element
2710        """
2711        return self._filtered_elements("descendant::text:bookmark-start")
2712
2713    def get_bookmark_start(
2714        self,
2715        position: int = 0,
2716        name: str | None = None,
2717    ) -> Element | None:
2718        """Return the bookmark start that matches the criteria.
2719
2720        Arguments:
2721
2722            position -- int
2723
2724            name -- str
2725
2726        Return: Element or None if not found
2727        """
2728        return self._filtered_element(
2729            "descendant::text:bookmark-start", position, text_name=name
2730        )
2731
2732    def get_bookmark_ends(self) -> list[Element]:
2733        """Return all the bookmark ends.
2734
2735        Return: list of Element
2736        """
2737        return self._filtered_elements("descendant::text:bookmark-end")
2738
2739    def get_bookmark_end(
2740        self,
2741        position: int = 0,
2742        name: str | None = None,
2743    ) -> Element | None:
2744        """Return the bookmark end that matches the criteria.
2745
2746        Arguments:
2747
2748            position -- int
2749
2750            name -- str
2751
2752        Return: Element or None if not found
2753        """
2754        return self._filtered_element(
2755            "descendant::text:bookmark-end", position, text_name=name
2756        )
2757
2758    # Reference marks
2759
2760    def get_reference_marks_single(self) -> list[Element]:
2761        """Return all the reference marks. Search only the tags
2762        text:reference-mark.
2763        Consider using : get_reference_marks()
2764
2765        Return: list of Element
2766        """
2767        return self._filtered_elements("descendant::text:reference-mark")
2768
2769    def get_reference_mark_single(
2770        self,
2771        position: int = 0,
2772        name: str | None = None,
2773    ) -> Element | None:
2774        """Return the reference mark that matches the criteria. Search only the
2775        tags text:reference-mark.
2776        Consider using : get_reference_mark()
2777
2778        Arguments:
2779
2780            position -- int
2781
2782            name -- str
2783
2784        Return: Element or None if not found
2785        """
2786        return self._filtered_element(
2787            "descendant::text:reference-mark", position, text_name=name
2788        )
2789
2790    def get_reference_mark_starts(self) -> list[Element]:
2791        """Return all the reference mark starts. Search only the tags
2792        text:reference-mark-start.
2793        Consider using : get_reference_marks()
2794
2795        Return: list of Element
2796        """
2797        return self._filtered_elements("descendant::text:reference-mark-start")
2798
2799    def get_reference_mark_start(
2800        self,
2801        position: int = 0,
2802        name: str | None = None,
2803    ) -> Element | None:
2804        """Return the reference mark start that matches the criteria. Search
2805        only the tags text:reference-mark-start.
2806        Consider using : get_reference_mark()
2807
2808        Arguments:
2809
2810            position -- int
2811
2812            name -- str
2813
2814        Return: Element or None if not found
2815        """
2816        return self._filtered_element(
2817            "descendant::text:reference-mark-start", position, text_name=name
2818        )
2819
2820    def get_reference_mark_ends(self) -> list[Element]:
2821        """Return all the reference mark ends. Search only the tags
2822        text:reference-mark-end.
2823        Consider using : get_reference_marks()
2824
2825        Return: list of Element
2826        """
2827        return self._filtered_elements("descendant::text:reference-mark-end")
2828
2829    def get_reference_mark_end(
2830        self,
2831        position: int = 0,
2832        name: str | None = None,
2833    ) -> Element | None:
2834        """Return the reference mark end that matches the criteria. Search only
2835        the tags text:reference-mark-end.
2836        Consider using : get_reference_marks()
2837
2838        Arguments:
2839
2840            position -- int
2841
2842            name -- str
2843
2844        Return: Element or None if not found
2845        """
2846        return self._filtered_element(
2847            "descendant::text:reference-mark-end", position, text_name=name
2848        )
2849
2850    def get_reference_marks(self) -> list[Element]:
2851        """Return all the reference marks, either single position reference
2852        (text:reference-mark) or start of range reference
2853        (text:reference-mark-start).
2854
2855        Return: list of Element
2856        """
2857        return self._filtered_elements(
2858            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2859        )
2860
2861    def get_reference_mark(
2862        self,
2863        position: int = 0,
2864        name: str | None = None,
2865    ) -> Element | None:
2866        """Return the reference mark that match the criteria. Either single
2867        position reference mark (text:reference-mark) or start of range
2868        reference (text:reference-mark-start).
2869
2870        Arguments:
2871
2872            position -- int
2873
2874            name -- str
2875
2876        Return: Element or None if not found
2877        """
2878        if name:
2879            request = (
2880                f"descendant::text:reference-mark-start"
2881                f'[@text:name="{name}"] '
2882                f"| descendant::text:reference-mark"
2883                f'[@text:name="{name}"]'
2884            )
2885            return self._filtered_element(request, position=0)
2886        request = (
2887            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2888        )
2889        return self._filtered_element(request, position)
2890
2891    def get_references(self, name: str | None = None) -> list[Element]:
2892        """Return all the references (text:reference-ref). If name is
2893        provided, returns the references of that name.
2894
2895        Return: list of Element
2896
2897        Arguments:
2898
2899            name -- str or None
2900        """
2901        if name is None:
2902            return self._filtered_elements("descendant::text:reference-ref")
2903        request = f'descendant::text:reference-ref[@text:ref-name="{name}"]'
2904        return self._filtered_elements(request)
2905
2906    # Shapes elements
2907
2908    # Groups
2909
2910    def get_draw_groups(
2911        self,
2912        title: str | None = None,
2913        description: str | None = None,
2914        content: str | None = None,
2915    ) -> list[Element]:
2916        return self._filtered_elements(
2917            "descendant::draw:g",
2918            svg_title=title,
2919            svg_desc=description,
2920            content=content,
2921        )
2922
2923    def get_draw_group(
2924        self,
2925        position: int = 0,
2926        name: str | None = None,
2927        title: str | None = None,
2928        description: str | None = None,
2929        content: str | None = None,
2930    ) -> Element | None:
2931        return self._filtered_element(
2932            "descendant::draw:g",
2933            position,
2934            draw_name=name,
2935            svg_title=title,
2936            svg_desc=description,
2937            content=content,
2938        )
2939
2940    # Lines
2941
2942    def get_draw_lines(
2943        self,
2944        draw_style: str | None = None,
2945        draw_text_style: str | None = None,
2946        content: str | None = None,
2947    ) -> list[Element]:
2948        """Return all the draw lines that match the criteria.
2949
2950        Arguments:
2951
2952            draw_style -- str
2953
2954            draw_text_style -- str
2955
2956            content -- str regex
2957
2958        Return: list of odf_shape
2959        """
2960        return self._filtered_elements(
2961            "descendant::draw:line",
2962            draw_style=draw_style,
2963            draw_text_style=draw_text_style,
2964            content=content,
2965        )
2966
2967    def get_draw_line(
2968        self,
2969        position: int = 0,
2970        id: str | None = None,  # noqa:A002
2971        content: str | None = None,
2972    ) -> Element | None:
2973        """Return the draw line that matches the criteria.
2974
2975        Arguments:
2976
2977            position -- int
2978
2979            id -- str
2980
2981            content -- str regex
2982
2983        Return: odf_shape or None if not found
2984        """
2985        return self._filtered_element(
2986            "descendant::draw:line", position, draw_id=id, content=content
2987        )
2988
2989    # Rectangles
2990
2991    def get_draw_rectangles(
2992        self,
2993        draw_style: str | None = None,
2994        draw_text_style: str | None = None,
2995        content: str | None = None,
2996    ) -> list[Element]:
2997        """Return all the draw rectangles that match the criteria.
2998
2999        Arguments:
3000
3001            draw_style -- str
3002
3003            draw_text_style -- str
3004
3005            content -- str regex
3006
3007        Return: list of odf_shape
3008        """
3009        return self._filtered_elements(
3010            "descendant::draw:rect",
3011            draw_style=draw_style,
3012            draw_text_style=draw_text_style,
3013            content=content,
3014        )
3015
3016    def get_draw_rectangle(
3017        self,
3018        position: int = 0,
3019        id: str | None = None,  # noqa:A002
3020        content: str | None = None,
3021    ) -> Element | None:
3022        """Return the draw rectangle that matches the criteria.
3023
3024        Arguments:
3025
3026            position -- int
3027
3028            id -- str
3029
3030            content -- str regex
3031
3032        Return: odf_shape or None if not found
3033        """
3034        return self._filtered_element(
3035            "descendant::draw:rect", position, draw_id=id, content=content
3036        )
3037
3038    # Ellipse
3039
3040    def get_draw_ellipses(
3041        self,
3042        draw_style: str | None = None,
3043        draw_text_style: str | None = None,
3044        content: str | None = None,
3045    ) -> list[Element]:
3046        """Return all the draw ellipses that match the criteria.
3047
3048        Arguments:
3049
3050            draw_style -- str
3051
3052            draw_text_style -- str
3053
3054            content -- str regex
3055
3056        Return: list of odf_shape
3057        """
3058        return self._filtered_elements(
3059            "descendant::draw:ellipse",
3060            draw_style=draw_style,
3061            draw_text_style=draw_text_style,
3062            content=content,
3063        )
3064
3065    def get_draw_ellipse(
3066        self,
3067        position: int = 0,
3068        id: str | None = None,  # noqa:A002
3069        content: str | None = None,
3070    ) -> Element | None:
3071        """Return the draw ellipse that matches the criteria.
3072
3073        Arguments:
3074
3075            position -- int
3076
3077            id -- str
3078
3079            content -- str regex
3080
3081        Return: odf_shape or None if not found
3082        """
3083        return self._filtered_element(
3084            "descendant::draw:ellipse", position, draw_id=id, content=content
3085        )
3086
3087    # Connectors
3088
3089    def get_draw_connectors(
3090        self,
3091        draw_style: str | None = None,
3092        draw_text_style: str | None = None,
3093        content: str | None = None,
3094    ) -> list[Element]:
3095        """Return all the draw connectors that match the criteria.
3096
3097        Arguments:
3098
3099            draw_style -- str
3100
3101            draw_text_style -- str
3102
3103            content -- str regex
3104
3105        Return: list of odf_shape
3106        """
3107        return self._filtered_elements(
3108            "descendant::draw:connector",
3109            draw_style=draw_style,
3110            draw_text_style=draw_text_style,
3111            content=content,
3112        )
3113
3114    def get_draw_connector(
3115        self,
3116        position: int = 0,
3117        id: str | None = None,  # noqa:A002
3118        content: str | None = None,
3119    ) -> Element | None:
3120        """Return the draw connector that matches the criteria.
3121
3122        Arguments:
3123
3124            position -- int
3125
3126            id -- str
3127
3128            content -- str regex
3129
3130        Return: odf_shape or None if not found
3131        """
3132        return self._filtered_element(
3133            "descendant::draw:connector", position, draw_id=id, content=content
3134        )
3135
3136    def get_orphan_draw_connectors(self) -> list[Element]:
3137        """Return a list of connectors which don't have any shape connected
3138        to them.
3139        """
3140        connectors = []
3141        for connector in self.get_draw_connectors():
3142            start_shape = connector.get_attribute("draw:start-shape")
3143            end_shape = connector.get_attribute("draw:end-shape")
3144            if start_shape is None and end_shape is None:
3145                connectors.append(connector)
3146        return connectors
3147
3148    # Tracked changes and text change
3149
3150    def get_tracked_changes(self) -> Element | None:
3151        """Return the tracked-changes part in the text body."""
3152        return self.get_element("//text:tracked-changes")
3153
3154    def get_changes_ids(self) -> list[Element | Text]:
3155        """Return a list of ids that refers to a change region in the tracked
3156        changes list.
3157        """
3158        # Insertion changes
3159        xpath_query = "descendant::text:change-start/@text:change-id"
3160        # Deletion changes
3161        xpath_query += " | descendant::text:change/@text:change-id"
3162        return self.xpath(xpath_query)
3163
3164    def get_text_change_deletions(self) -> list[Element]:
3165        """Return all the text changes of deletion kind: the tags text:change.
3166        Consider using : get_text_changes()
3167
3168        Return: list of Element
3169        """
3170        return self._filtered_elements("descendant::text:text:change")
3171
3172    def get_text_change_deletion(
3173        self,
3174        position: int = 0,
3175        idx: str | None = None,
3176    ) -> Element | None:
3177        """Return the text change of deletion kind that matches the criteria.
3178        Search only for the tags text:change.
3179        Consider using : get_text_change()
3180
3181        Arguments:
3182
3183            position -- int
3184
3185            idx -- str
3186
3187        Return: Element or None if not found
3188        """
3189        return self._filtered_element(
3190            "descendant::text:change", position, change_id=idx
3191        )
3192
3193    def get_text_change_starts(self) -> list[Element]:
3194        """Return all the text change-start. Search only for the tags
3195        text:change-start.
3196        Consider using : get_text_changes()
3197
3198        Return: list of Element
3199        """
3200        return self._filtered_elements("descendant::text:change-start")
3201
3202    def get_text_change_start(
3203        self,
3204        position: int = 0,
3205        idx: str | None = None,
3206    ) -> Element | None:
3207        """Return the text change-start that matches the criteria. Search
3208        only the tags text:change-start.
3209        Consider using : get_text_change()
3210
3211        Arguments:
3212
3213            position -- int
3214
3215            idx -- str
3216
3217        Return: Element or None if not found
3218        """
3219        return self._filtered_element(
3220            "descendant::text:change-start", position, change_id=idx
3221        )
3222
3223    def get_text_change_ends(self) -> list[Element]:
3224        """Return all the text change-end. Search only the tags
3225        text:change-end.
3226        Consider using : get_text_changes()
3227
3228        Return: list of Element
3229        """
3230        return self._filtered_elements("descendant::text:change-end")
3231
3232    def get_text_change_end(
3233        self,
3234        position: int = 0,
3235        idx: str | None = None,
3236    ) -> Element | None:
3237        """Return the text change-end that matches the criteria. Search only
3238        the tags text:change-end.
3239        Consider using : get_text_change()
3240
3241        Arguments:
3242
3243            position -- int
3244
3245            idx -- str
3246
3247        Return: Element or None if not found
3248        """
3249        return self._filtered_element(
3250            "descendant::text:change-end", position, change_id=idx
3251        )
3252
3253    def get_text_changes(self) -> list[Element]:
3254        """Return all the text changes, either single deletion
3255        (text:change) or start of range of changes (text:change-start).
3256
3257        Return: list of Element
3258        """
3259        request = "descendant::text:change-start | descendant::text:change"
3260        return self._filtered_elements(request)
3261
3262    def get_text_change(
3263        self,
3264        position: int = 0,
3265        idx: str | None = None,
3266    ) -> Element | None:
3267        """Return the text change that matches the criteria. Either single
3268        deletion (text:change) or start of range of changes (text:change-start).
3269        position : index of the element to retrieve if several matches, default
3270        is 0.
3271        idx : change-id of the element.
3272
3273        Arguments:
3274
3275            position -- int
3276
3277            idx -- str
3278
3279        Return: Element or None if not found
3280        """
3281        if idx:
3282            request = (
3283                f'descendant::text:change-start[@text:change-id="{idx}"] '
3284                f'| descendant::text:change[@text:change-id="{idx}"]'
3285            )
3286            return self._filtered_element(request, 0)
3287        request = "descendant::text:change-start | descendant::text:change"
3288        return self._filtered_element(request, position)
3289
3290    # Table Of Content
3291
3292    def get_tocs(self) -> list[Element]:
3293        """Return all the tables of contents.
3294
3295        Return: list of odf_toc
3296        """
3297        return self._filtered_elements("text:table-of-content")
3298
3299    def get_toc(
3300        self,
3301        position: int = 0,
3302        content: str | None = None,
3303    ) -> Element | None:
3304        """Return the table of contents that matches the criteria.
3305
3306        Arguments:
3307
3308            position -- int
3309
3310            content -- str regex
3311
3312        Return: odf_toc or None if not found
3313        """
3314        return self._filtered_element(
3315            "text:table-of-content", position, content=content
3316        )
3317
3318    # Styles
3319
3320    @staticmethod
3321    def _get_style_tagname(family: str | None, is_default: bool = False) -> str:
3322        """Widely match possible tag names given the family (or not)."""
3323        if not family:
3324            tagname = "(style:default-style|*[@style:name]|draw:fill-image|draw:marker)"
3325        elif is_default:
3326            # Default style
3327            tagname = "style:default-style"
3328        else:
3329            tagname = _family_style_tagname(family)
3330            # if famattr:
3331            #    # Include family default style
3332            #    tagname = '(%s|style:default-style)' % tagname
3333            if family in FAMILY_ODF_STD:
3334                # Include family default style
3335                tagname = f"({tagname}|style:default-style)"
3336        return tagname
3337
3338    def get_styles(self, family: str | None = None) -> list[Element]:
3339        # Both common and default styles
3340        tagname = self._get_style_tagname(family)
3341        return self._filtered_elements(tagname, family=family)
3342
3343    def get_style(
3344        self,
3345        family: str,
3346        name_or_element: str | Element | None = None,
3347        display_name: str | None = None,
3348    ) -> Element | None:
3349        """Return the style uniquely identified by the family/name pair. If
3350        the argument is already a style object, it will return it.
3351
3352        If the name is not the internal name but the name you gave in the
3353        desktop application, use display_name instead.
3354
3355        Arguments:
3356
3357            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
3358                      'number'
3359
3360            name_or_element -- str or Style
3361
3362            display_name -- str
3363
3364        Return: odf_style or None if not found
3365        """
3366        if isinstance(name_or_element, Element):
3367            name = self.get_attribute("style:name")
3368            if name is not None:
3369                return name_or_element
3370            else:
3371                raise ValueError(f"Not a odf_style ? {name_or_element!r}")
3372        style_name = name_or_element
3373        is_default = not (style_name or display_name)
3374        tagname = self._get_style_tagname(family, is_default=is_default)
3375        # famattr became None if no "style:family" attribute
3376        if family:
3377            return self._filtered_element(
3378                tagname,
3379                0,
3380                style_name=style_name,
3381                display_name=display_name,
3382                family=family,
3383            )
3384        else:
3385            return self._filtered_element(
3386                tagname,
3387                0,
3388                draw_name=style_name or display_name,
3389                family=family,
3390            )
3391
3392    def _filtered_element(
3393        self,
3394        query_string: str,
3395        position: int,
3396        **kwargs: Any,
3397    ) -> Element | None:
3398        results = self._filtered_elements(query_string, **kwargs)
3399        try:
3400            return results[position]
3401        except IndexError:
3402            return None
3403
3404    def _filtered_elements(
3405        self,
3406        query_string: str,
3407        content: str | None = None,
3408        url: str | None = None,
3409        svg_title: str | None = None,
3410        svg_desc: str | None = None,
3411        dc_creator: str | None = None,
3412        dc_date: datetime | None = None,
3413        **kwargs: Any,
3414    ) -> list[Element]:
3415        query = make_xpath_query(query_string, **kwargs)
3416        elements = self.get_elements(query)
3417        # Filter the elements with the regex (TODO use XPath)
3418        if content is not None:
3419            elements = [element for element in elements if element.match(content)]
3420        if url is not None:
3421            filtered = []
3422            for element in elements:
3423                url_attr = element.get_attribute("xlink:href")
3424                if isinstance(url_attr, str) and search(url, url_attr) is not None:
3425                    filtered.append(element)
3426            elements = filtered
3427        if dc_date is None:
3428            dt_dc_date = None
3429        else:
3430            dt_dc_date = DateTime.encode(dc_date)
3431        for variable, childname in [
3432            (svg_title, "svg:title"),
3433            (svg_desc, "svg:desc"),
3434            (dc_creator, "descendant::dc:creator"),
3435            (dt_dc_date, "descendant::dc:date"),
3436        ]:
3437            if not variable:
3438                continue
3439            filtered = []
3440            for element in elements:
3441                child = element.get_element(childname)
3442                if child and child.match(variable):
3443                    filtered.append(element)
3444            elements = filtered
3445        return elements

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Element(**kwargs: Any)
316    def __init__(self, **kwargs: Any) -> None:
317        tag_or_elem = kwargs.pop("tag_or_elem", None)
318        if tag_or_elem is None:
319            # Instance for newly created object: create new lxml element and
320            # continue by subclass __init__
321            # If the tag key word exists, make a custom element
322            self._do_init = True
323            tag = kwargs.pop("tag", self._tag)
324            self.__element = self.make_etree_element(tag)
325        else:
326            # called with an existing lxml element, sould be a result of
327            # from_tag() casting, do not execute the subclass __init__
328            if not isinstance(tag_or_elem, _Element):
329                raise TypeError(f'"{type(tag_or_elem)}" is not an element node')
330            self._do_init = False
331            self.__element = tag_or_elem
@classmethod
def from_tag(cls, tag_or_elem: str | lxml.etree._Element) -> Element:
339    @classmethod
340    def from_tag(cls, tag_or_elem: str | _Element) -> Element:
341        """Element class and subclass factory.
342
343        Turn an lxml Element or ODF string tag into an ODF XML Element
344        of the relevant class.
345
346        Arguments:
347
348            tag_or_elem -- ODF str tag or lxml.Element
349
350        Return: Element (or subclass) instance
351        """
352        if isinstance(tag_or_elem, str):
353            # assume the argument is a prefix:name tag
354            elem = cls.make_etree_element(tag_or_elem)
355        else:
356            elem = tag_or_elem
357        klass = _class_registry.get(elem.tag, cls)
358        return klass(tag_or_elem=elem)

Element class and subclass factory.

Turn an lxml Element or ODF string tag into an ODF XML Element of the relevant class.

Arguments:

tag_or_elem -- ODF str tag or lxml.Element

Return: Element (or subclass) instance

@classmethod
def from_tag_for_clone( cls: type, tree_element: lxml.etree._Element, cache: tuple | None) -> Element:
360    @classmethod
361    def from_tag_for_clone(
362        cls: type,
363        tree_element: _Element,
364        cache: tuple | None,
365    ) -> Element:
366        tag = to_str(tree_element.tag)
367        klass = _class_registry.get(tag, cls)
368        element = klass(tag_or_elem=tree_element)
369        if cache and element._caching:
370            element._tmap = cache[0]
371            element._cmap = cache[1]
372            if len(cache) == 3:
373                element._rmap = cache[2]
374        return element
@staticmethod
def make_etree_element(tag: str) -> lxml.etree._Element:
376    @staticmethod
377    def make_etree_element(tag: str) -> _Element:
378        if not isinstance(tag, str):
379            raise TypeError(f"Tag is not str: {tag!r}")
380        tag = tag.strip()
381        if not tag:
382            raise ValueError("Tag is empty")
383        if "<" not in tag:
384            # Qualified name
385            # XXX don't build the element from scratch or lxml will pollute with
386            # repeated namespace declarations
387            tag = f"<{tag}/>"
388        # XML fragment
389        root = fromstring(NAMESPACES_XML % str_to_bytes(tag))
390        return root[0]
tag: str
753    @property
754    def tag(self) -> str:
755        """Get/set the underlying xml tag with the given qualified name.
756
757        Warning: direct change of tag does not change the element class.
758
759        Arguments:
760
761            qname -- str (e.g. "text:span")
762        """
763        return _get_prefixed_name(self.__element.tag)

Get/set the underlying xml tag with the given qualified name.

Warning: direct change of tag does not change the element class.

Arguments:

qname -- str (e.g. "text:span")
def elements_repeated_sequence( self, xpath_instance: lxml.etree.XPath, name: str) -> list[tuple[int, int]]:
769    def elements_repeated_sequence(
770        self,
771        xpath_instance: XPath,
772        name: str,
773    ) -> list[tuple[int, int]]:
774        """Utility method for table module."""
775        lxml_tag = _get_lxml_tag_or_name(name)
776        element = self.__element
777        sub_elements = xpath_instance(element)
778        if not isinstance(sub_elements, list):
779            raise TypeError("Bad XPath result.")
780        result: list[tuple[int, int]] = []
781        idx = -1
782        for sub_element in sub_elements:
783            if not isinstance(sub_element, _Element):
784                continue
785            idx += 1
786            value = sub_element.get(lxml_tag)
787            if value is None:
788                result.append((idx, 1))
789                continue
790            try:
791                int_value = int(value)
792            except ValueError:
793                int_value = 1
794            result.append((idx, max(int_value, 1)))
795        return result

Utility method for table module.

def get_elements(self, xpath_query: lxml.etree.XPath | str) -> list[Element]:
797    def get_elements(self, xpath_query: XPath | str) -> list[Element]:
798        cache: tuple | None = None
799        element = self.__element
800        if isinstance(xpath_query, str):
801            new_xpath_query = xpath_compile(xpath_query)
802            result = new_xpath_query(element)
803        else:
804            result = xpath_query(element)
805        if not isinstance(result, list):
806            raise TypeError("Bad XPath result")
807
808        if hasattr(self, "_tmap"):
809            if hasattr(self, "_rmap"):
810                cache = (self._tmap, self._cmap, self._rmap)
811            else:
812                cache = (self._tmap, self._cmap)
813        return [
814            Element.from_tag_for_clone(e, cache)
815            for e in result
816            if isinstance(e, _Element)
817        ]
def get_element( self, xpath_query: lxml.etree.XPath | str) -> Element | None:
821    def get_element(self, xpath_query: XPath | str) -> Element | None:
822        element = self.__element
823        result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES)
824        if result:
825            return Element.from_tag(result[0])  # type:ignore
826        return None
attributes: dict[str, str]
842    @property
843    def attributes(self) -> dict[str, str]:
844        return {
845            _get_prefixed_name(str(key)): str(value)
846            for key, value in self.__element.attrib.items()
847        }
def get_attribute(self, name: str) -> str | bool | None:
849    def get_attribute(self, name: str) -> str | bool | None:
850        """Return the attribute value as type str | bool | None."""
851        element = self.__element
852        lxml_tag = _get_lxml_tag_or_name(name)
853        value = element.get(lxml_tag)
854        if value is None:
855            return None
856        elif value in ("true", "false"):
857            return Boolean.decode(value)
858        return str(value)

Return the attribute value as type str | bool | None.

def get_attribute_integer(self, name: str) -> int | None:
860    def get_attribute_integer(self, name: str) -> int | None:
861        """Return either the attribute as type int, or None."""
862        element = self.__element
863        lxml_tag = _get_lxml_tag_or_name(name)
864        value = element.get(lxml_tag)
865        if value is None:
866            return None
867        try:
868            return int(value)
869        except ValueError:
870            return None

Return either the attribute as type int, or None.

def get_attribute_string(self, name: str) -> str | None:
872    def get_attribute_string(self, name: str) -> str | None:
873        """Return either the attribute as type str, or None."""
874        element = self.__element
875        lxml_tag = _get_lxml_tag_or_name(name)
876        value = element.get(lxml_tag)
877        if value is None:
878            return None
879        return str(value)

Return either the attribute as type str, or None.

def set_attribute(self, name: str, value: bool | str | tuple[int, int, int] | None) -> None:
881    def set_attribute(
882        self, name: str, value: bool | str | tuple[int, int, int] | None
883    ) -> None:
884        if name in ODF_COLOR_PROPERTY:
885            if isinstance(value, bool):
886                raise TypeError(f"Wrong color type {value!r}")
887            if value != "transparent":
888                value = hexa_color(value)
889        element = self.__element
890        lxml_tag = _get_lxml_tag_or_name(name)
891        if isinstance(value, bool):
892            value = Boolean.encode(value)
893        elif value is None:
894            with contextlib.suppress(KeyError):
895                del element.attrib[lxml_tag]
896            return
897        element.set(lxml_tag, str(value))
def set_style_attribute(self, name: str, value: Element | str) -> None:
899    def set_style_attribute(self, name: str, value: Element | str) -> None:
900        """Shortcut to accept a style object as a value."""
901        if isinstance(value, Element):
902            value = str(value.name)  # type:ignore
903        return self.set_attribute(name, value)

Shortcut to accept a style object as a value.

def del_attribute(self, name: str) -> None:
905    def del_attribute(self, name: str) -> None:
906        element = self.__element
907        lxml_tag = _get_lxml_tag_or_name(name)
908        del element.attrib[lxml_tag]
text: str
910    @property
911    def text(self) -> str:
912        """Get / set the text content of the element."""
913        return self.__element.text or ""

Get / set the text content of the element.

text_recursive: str
924    @property
925    def text_recursive(self) -> str:
926        return "".join(str(x) for x in self.__element.itertext())
tail: str | None
928    @property
929    def tail(self) -> str | None:
930        """Get / set the text immediately following the element."""
931        return self.__element.tail

Get / set the text immediately following the element.

def search(self, pattern: str) -> int | None:
937    def search(self, pattern: str) -> int | None:
938        """Return the first position of the pattern in the text content of
939        the element, or None if not found.
940
941        Python regular expression syntax applies.
942
943        Arguments:
944
945            pattern -- str
946
947        Return: int or None
948        """
949        match = re.search(pattern, self.text_recursive)
950        if match is None:
951            return None
952        return match.start()

Return the first position of the pattern in the text content of the element, or None if not found.

Python regular expression syntax applies.

Arguments:

pattern -- str

Return: int or None

def match(self, pattern: str) -> bool:
954    def match(self, pattern: str) -> bool:
955        """return True if the pattern is found one or more times anywhere in
956        the text content of the element.
957
958        Python regular expression syntax applies.
959
960        Arguments:
961
962            pattern -- str
963
964        Return: bool
965        """
966        return self.search(pattern) is not None

return True if the pattern is found one or more times anywhere in the text content of the element.

Python regular expression syntax applies.

Arguments:

pattern -- str

Return: bool

def replace(self, pattern: str, new: str | None = None) -> int:
 968    def replace(self, pattern: str, new: str | None = None) -> int:
 969        """Replace the pattern with the given text, or delete if text is an
 970        empty string, and return the number of replacements. By default, only
 971        return the number of occurences that would be replaced.
 972
 973        It cannot replace patterns found across several element, like a word
 974        split into two consecutive spans.
 975
 976        Python regular expression syntax applies.
 977
 978        Arguments:
 979
 980            pattern -- str
 981
 982            new -- str
 983
 984        Return: int
 985        """
 986        if not isinstance(pattern, str):
 987            # Fail properly if the pattern is an non-ascii bytestring
 988            pattern = str(pattern)
 989        cpattern = re.compile(pattern)
 990        count = 0
 991        for text in self.xpath("descendant::text()"):
 992            if new is None:
 993                count += len(cpattern.findall(str(text)))
 994            else:
 995                new_text, number = cpattern.subn(new, str(text))
 996                container = text.parent
 997                if text.is_text():  # type: ignore
 998                    container.text = new_text  # type: ignore
 999                else:
1000                    container.tail = new_text  # type: ignore
1001                count += number
1002        return count

Replace the pattern with the given text, or delete if text is an empty string, and return the number of replacements. By default, only return the number of occurences that would be replaced.

It cannot replace patterns found across several element, like a word split into two consecutive spans.

Python regular expression syntax applies.

Arguments:

pattern -- str

new -- str

Return: int

root: Element
1004    @property
1005    def root(self) -> Element:
1006        element = self.__element
1007        tree = element.getroottree()
1008        root = tree.getroot()
1009        return Element.from_tag(root)
parent: Element | None
1011    @property
1012    def parent(self) -> Element | None:
1013        element = self.__element
1014        parent = element.getparent()
1015        if parent is None:
1016            # Already at root
1017            return None
1018        return Element.from_tag(parent)
is_bound: bool
1020    @property
1021    def is_bound(self) -> bool:
1022        return self.parent is not None
children: list[Element]
1038    @property
1039    def children(self) -> list[Element]:
1040        element = self.__element
1041        return [
1042            Element.from_tag(e)
1043            for e in element.iterchildren()
1044            if isinstance(e, _Element)
1045        ]
def index(self, child: Element) -> int:
1047    def index(self, child: Element) -> int:
1048        """Return the position of the child in this element.
1049
1050        Inspired by lxml
1051        """
1052        return self.__element.index(child.__element)

Return the position of the child in this element.

Inspired by lxml

text_content: str
1054    @property
1055    def text_content(self) -> str:
1056        """Get / set the text of the embedded paragraph, including embeded
1057        annotations, cells...
1058
1059        Set create a paragraph if missing
1060        """
1061        return "\n".join(
1062            child.text_recursive for child in self.get_elements("descendant::text:p")
1063        )

Get / set the text of the embedded paragraph, including embeded annotations, cells...

Set create a paragraph if missing

def is_empty(self) -> bool:
1095    def is_empty(self) -> bool:
1096        """Check if the element is empty : no text, no children, no tail.
1097
1098        Return: Boolean
1099        """
1100        element = self.__element
1101        if element.tail is not None:
1102            return False
1103        if element.text is not None:
1104            return False
1105        if list(element.iterchildren()):
1106            return False
1107        return True

Check if the element is empty : no text, no children, no tail.

Return: Boolean

def get_between( self, tag1: Element, tag2: Element, as_text: bool = False, clean: bool = True, no_header: bool = True) -> list | Element | str:
1252    def get_between(
1253        self,
1254        tag1: Element,
1255        tag2: Element,
1256        as_text: bool = False,
1257        clean: bool = True,
1258        no_header: bool = True,
1259    ) -> list | Element | str:
1260        """Returns elements between tag1 and tag2, tag1 and tag2 shall
1261        be unique and having an id attribute.
1262        (WARN: buggy if tag1/tag2 defines a malformed odf xml.)
1263        If as_text is True: returns the text content.
1264        If clean is True: suppress unwanted tags (deletions marks, ...)
1265        If no_header is True: existing text:h are changed in text:p
1266        By default: returns a list of Element, cleaned and without headers.
1267
1268        Implementation and standard retrictions:
1269        Only text:h and text:p sould be 'cut' by an insert tag, so inner parts
1270        of insert tags are:
1271
1272            - any text:h, text:p or sub tag of these
1273
1274            - some text, part of a parent text:h or text:p
1275
1276        Arguments:
1277
1278            tag1 -- Element
1279
1280            tag2 -- Element
1281
1282            as_text -- boolean
1283
1284            clean -- boolean
1285
1286            no_header -- boolean
1287
1288        Return: list of odf_paragraph or odf_header
1289        """
1290        inner = self._get_between_base(tag1, tag2)
1291
1292        if clean:
1293            clean_tags = (
1294                "text:change",
1295                "text:change-start",
1296                "text:change-end",
1297                "text:reference-mark",
1298                "text:reference-mark-start",
1299                "text:reference-mark-end",
1300            )
1301            request_self = " | ".join(["self::%s" % c for c in clean_tags])
1302            inner = [e for e in inner if not e.xpath(request_self)]
1303            request = " | ".join([f"descendant::{tag}" for tag in clean_tags])
1304            for element in inner:
1305                to_del = element.xpath(request)
1306                for elem in to_del:
1307                    if isinstance(elem, Element):
1308                        element.delete(elem)
1309        if no_header:  # crude replace t:h by t:p
1310            new_inner = []
1311            for element in inner:
1312                if element.tag == "text:h":
1313                    children = element.children
1314                    text = element.__element.text
1315                    para = Element.from_tag("text:p")
1316                    para.text = text or ""
1317                    for c in children:
1318                        para.append(c)
1319                    new_inner.append(para)
1320                else:
1321                    new_inner.append(element)
1322            inner = new_inner
1323        if as_text:
1324            return "\n".join([e.get_formatted_text() for e in inner])
1325        else:
1326            return inner

Returns elements between tag1 and tag2, tag1 and tag2 shall be unique and having an id attribute. (WARN: buggy if tag1/tag2 defines a malformed odf xml.) If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and without headers.

Implementation and standard retrictions: Only text:h and text:p sould be 'cut' by an insert tag, so inner parts of insert tags are:

- any text:h, text:p or sub tag of these

- some text, part of a parent text:h or text:p

Arguments:

tag1 -- Element

tag2 -- Element

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list of odf_paragraph or odf_header

def insert( self, element: Element, xmlposition: int | None = None, position: int | None = None, start: bool = False) -> None:
1328    def insert(
1329        self,
1330        element: Element,
1331        xmlposition: int | None = None,
1332        position: int | None = None,
1333        start: bool = False,
1334    ) -> None:
1335        """Insert an element relatively to ourself.
1336
1337        Insert either using DOM vocabulary or by numeric position.
1338        If text start is True, insert the element before any existing text.
1339
1340        Position start at 0.
1341
1342        Arguments:
1343
1344            element -- Element
1345
1346            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
1347                           or PREV_SIBLING
1348
1349            start -- Boolean
1350
1351            position -- int
1352        """
1353        # child_tag = element.tag
1354        current = self.__element
1355        _element = element.__element
1356        if start:
1357            text = current.text
1358            if text is not None:
1359                current.text = None
1360                tail = _element.tail
1361                if tail is None:
1362                    tail = text
1363                else:
1364                    tail = tail + text
1365                _element.tail = tail
1366            position = 0
1367        if position is not None:
1368            current.insert(position, _element)
1369        elif xmlposition is FIRST_CHILD:
1370            current.insert(0, _element)
1371        elif xmlposition is LAST_CHILD:
1372            current.append(_element)
1373        elif xmlposition is NEXT_SIBLING:
1374            parent = current.getparent()
1375            index = parent.index(current)  # type: ignore
1376            parent.insert(index + 1, _element)  # type: ignore
1377        elif xmlposition is PREV_SIBLING:
1378            parent = current.getparent()
1379            index = parent.index(current)  # type: ignore
1380            parent.insert(index, _element)  # type: ignore
1381        else:
1382            raise ValueError("(xml)position must be defined")

Insert an element relatively to ourself.

Insert either using DOM vocabulary or by numeric position. If text start is True, insert the element before any existing text.

Position start at 0.

Arguments:

element -- Element

xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
               or PREV_SIBLING

start -- Boolean

position -- int
def extend( self, odf_elements: collections.abc.Iterable[Element]) -> None:
1384    def extend(self, odf_elements: Iterable[Element]) -> None:
1385        """Fast append elements at the end of ourself using extend."""
1386        if odf_elements:
1387            current = self.__element
1388            elements = [element.__element for element in odf_elements]
1389            current.extend(elements)

Fast append elements at the end of ourself using extend.

def append(self, str_or_element: str | Element) -> None:
1391    def append(self, str_or_element: str | Element) -> None:
1392        """Insert element or text in the last position."""
1393        current = self.__element
1394        if isinstance(str_or_element, str):
1395            # Has children ?
1396            children = list(current.iterchildren())
1397            if children:
1398                # Append to tail of the last child
1399                last_child = children[-1]
1400                text = last_child.tail
1401                text = text if text is not None else ""
1402                text += str_or_element
1403                last_child.tail = text
1404            else:
1405                # Append to text of the element
1406                text = current.text
1407                text = text if text is not None else ""
1408                text += str_or_element
1409                current.text = text
1410        elif isinstance(str_or_element, Element):
1411            current.append(str_or_element.__element)
1412        else:
1413            raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')

Insert element or text in the last position.

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
1415    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
1416        """Delete the given element from the XML tree. If no element is given,
1417        "self" is deleted. The XML library may allow to continue to use an
1418        element now "orphan" as long as you have a reference to it.
1419
1420        if keep_tail is True (default), the tail text is not erased.
1421
1422        Arguments:
1423
1424            child -- Element
1425
1426            keep_tail -- boolean (default to True), True for most usages.
1427        """
1428        if child is None:
1429            parent = self.parent
1430            if parent is None:
1431                raise ValueError(f"Can't delete the root element\n{self.serialize()}")
1432            child = self
1433        else:
1434            parent = self
1435        if keep_tail and child.__element.tail is not None:
1436            current = child.__element
1437            tail = str(current.tail)
1438            current.tail = None
1439            prev = current.getprevious()
1440            if prev is not None:
1441                if prev.tail is None:
1442                    prev.tail = tail
1443                else:
1444                    prev.tail += tail
1445            else:
1446                if parent.__element.text is None:
1447                    parent.__element.text = tail
1448                else:
1449                    parent.__element.text += tail
1450        parent.__element.remove(child.__element)

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

if keep_tail is True (default), the tail text is not erased.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
def replace_element( self, old_element: Element, new_element: Element) -> None:
1452    def replace_element(self, old_element: Element, new_element: Element) -> None:
1453        """Replaces in place a sub element with the element passed as second
1454        argument.
1455
1456        Warning : no clone for old element.
1457        """
1458        current = self.__element
1459        current.replace(old_element.__element, new_element.__element)

Replaces in place a sub element with the element passed as second argument.

Warning : no clone for old element.

def strip_elements( self, sub_elements: Element | collections.abc.Iterable[Element]) -> Element | list:
1461    def strip_elements(
1462        self,
1463        sub_elements: Element | Iterable[Element],
1464    ) -> Element | list:
1465        """Remove the tags of provided elements, keeping inner childs and text.
1466
1467        Return : the striped element.
1468
1469        Warning : no clone in sub_elements list.
1470
1471        Arguments:
1472
1473            sub_elements -- Element or list of Element
1474        """
1475        if not sub_elements:
1476            return self
1477        if isinstance(sub_elements, Element):
1478            sub_elements = (sub_elements,)
1479        replacer = _get_lxml_tag("text:this-will-be-removed")
1480        for element in sub_elements:
1481            element.__element.tag = replacer
1482        strip = ("text:this-will-be-removed",)
1483        return self.strip_tags(strip=strip, default=None)

Remove the tags of provided elements, keeping inner childs and text.

Return : the striped element.

Warning : no clone in sub_elements list.

Arguments:

sub_elements -- Element or list of Element
def strip_tags( self, strip: collections.abc.Iterable[str] | None = None, protect: collections.abc.Iterable[str] | None = None, default: str | None = 'text:p') -> Element | list:
1485    def strip_tags(
1486        self,
1487        strip: Iterable[str] | None = None,
1488        protect: Iterable[str] | None = None,
1489        default: str | None = "text:p",
1490    ) -> Element | list:
1491        """Remove the tags listed in strip, recursively, keeping inner childs
1492        and text. Tags listed in protect stop the removal one level depth. If
1493        the first level element is stripped, default is used to embed the
1494        content in the default element. If default is None and first level is
1495        striped, a list of text and children is returned. Return : the striped
1496        element.
1497
1498        strip_tags should be used by on purpose methods (strip_span ...)
1499        (Method name taken from lxml).
1500
1501        Arguments:
1502
1503            strip -- iterable list of str odf tags, or None
1504
1505            protect -- iterable list of str odf tags, or None
1506
1507            default -- str odf tag, or None
1508
1509        Return:
1510
1511            Element.
1512        """
1513        if not strip:
1514            return self
1515        if not protect:
1516            protect = ()
1517        protected = False
1518        element, modified = Element._strip_tags(self, strip, protect, protected)
1519        if modified and isinstance(element, list) and default:
1520            new = Element.from_tag(default)
1521            for content in element:
1522                if isinstance(content, Element):
1523                    new.append(content)
1524                else:
1525                    new.text = content
1526            element = new
1527        return element

Remove the tags listed in strip, recursively, keeping inner childs and text. Tags listed in protect stop the removal one level depth. If the first level element is stripped, default is used to embed the content in the default element. If default is None and first level is striped, a list of text and children is returned. Return : the striped element.

strip_tags should be used by on purpose methods (strip_span ...) (Method name taken from lxml).

Arguments:

strip -- iterable list of str odf tags, or None

protect -- iterable list of str odf tags, or None

default -- str odf tag, or None

Return:

Element.
def xpath( self, xpath_query: str) -> list[Element | Text]:
1583    def xpath(self, xpath_query: str) -> list[Element | Text]:
1584        """Apply XPath query to the element and its subtree. Return list of
1585        Element or Text instances translated from the nodes found.
1586        """
1587        element = self.__element
1588        xpath_instance = xpath_compile(xpath_query)
1589        elements = xpath_instance(element)
1590        result: list[Element | Text] = []
1591        if hasattr(elements, "__iter__"):
1592            for obj in elements:  # type: ignore
1593                if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)):
1594                    result.append(Text(obj))
1595                elif isinstance(obj, _Element):
1596                    result.append(Element.from_tag(obj))
1597                # else:
1598                #     result.append(obj)
1599        return result

Apply XPath query to the element and its subtree. Return list of Element or Text instances translated from the nodes found.

def clear(self) -> None:
1601    def clear(self) -> None:
1602        """Remove text, children and attributes from the element."""
1603        self.__element.clear()
1604        if hasattr(self, "_tmap"):
1605            self._tmap: list[int] = []
1606        if hasattr(self, "_cmap"):
1607            self._cmap: list[int] = []
1608        if hasattr(self, "_rmap"):
1609            self._rmap: list[int] = []
1610        if hasattr(self, "_indexes"):
1611            remember = False
1612            if "_rmap" in self._indexes:
1613                remember = True
1614            self._indexes: dict[str, dict] = {}
1615            self._indexes["_cmap"] = {}
1616            self._indexes["_tmap"] = {}
1617            if remember:
1618                self._indexes["_rmap"] = {}

Remove text, children and attributes from the element.

clone: Element
1620    @property
1621    def clone(self) -> Element:
1622        clone = deepcopy(self.__element)
1623        root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES)
1624        root.append(clone)
1625        return self.from_tag(clone)
1626
1627        # slow data = tostring(self.__element, encoding='unicode')
1628        # return self.from_tag(data)
def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1635    def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1636        """Return text serialization of XML element."""
1637        # This copy bypasses serialization side-effects in lxml
1638        native = deepcopy(self.__element)
1639        data = tostring(
1640            native, with_tail=False, pretty_print=pretty, encoding="unicode"
1641        )
1642        if with_ns:
1643            return data
1644        # Remove namespaces
1645        return self._strip_namespaces(data)

Return text serialization of XML element.

document_body: Element | None
1649    @property
1650    def document_body(self) -> Element | None:
1651        """Return the document body : 'office:body'"""
1652        return self.get_element("//office:body/*[1]")

Return the document body : 'office:body'

def get_formatted_text(self, context: dict | None = None) -> str:
1667    def get_formatted_text(self, context: dict | None = None) -> str:
1668        """This function should return a beautiful version of the text."""
1669        return ""

This function should return a beautiful version of the text.

def get_styled_elements(self, name: str = '') -> list[Element]:
1671    def get_styled_elements(self, name: str = "") -> list[Element]:
1672        """Brute-force to find paragraphs, tables, etc. using the given style
1673        name (or all by default).
1674
1675        Arguments:
1676
1677            name -- str
1678
1679        Return: list
1680        """
1681        # FIXME incomplete (and possibly inaccurate)
1682        return (
1683            self._filtered_elements("descendant::*", text_style=name)
1684            + self._filtered_elements("descendant::*", draw_style=name)
1685            + self._filtered_elements("descendant::*", draw_text_style=name)
1686            + self._filtered_elements("descendant::*", table_style=name)
1687            + self._filtered_elements("descendant::*", page_layout=name)
1688            + self._filtered_elements("descendant::*", master_page=name)
1689            + self._filtered_elements("descendant::*", parent_style=name)
1690        )

Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).

Arguments:

name -- str

Return: list

dc_creator: str | None
1709    @property
1710    def dc_creator(self) -> str | None:
1711        """Get dc:creator value.
1712
1713        Return: str (or None if inexistant)
1714        """
1715        return self._get_inner_text("dc:creator")

Get dc:creator value.

Return: str (or None if inexistant)

dc_date: datetime.datetime | None
1727    @property
1728    def dc_date(self) -> datetime | None:
1729        """Get the dc:date value.
1730
1731        Return: datetime (or None if inexistant)
1732        """
1733        date = self._get_inner_text("dc:date")
1734        if date is None:
1735            return None
1736        return DateTime.decode(date)

Get the dc:date value.

Return: datetime (or None if inexistant)

svg_title: str | None
1750    @property
1751    def svg_title(self) -> str | None:
1752        return self._get_inner_text("svg:title")
svg_description: str | None
1758    @property
1759    def svg_description(self) -> str | None:
1760        return self._get_inner_text("svg:desc")
def get_sections( self, style: str | None = None, content: str | None = None) -> list[Element]:
1768    def get_sections(
1769        self,
1770        style: str | None = None,
1771        content: str | None = None,
1772    ) -> list[Element]:
1773        """Return all the sections that match the criteria.
1774
1775        Arguments:
1776
1777            style -- str
1778
1779            content -- str regex
1780
1781        Return: list of Element
1782        """
1783        return self._filtered_elements(
1784            "text:section", text_style=style, content=content
1785        )

Return all the sections that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Element

def get_section( self, position: int = 0, content: str | None = None) -> Element | None:
1787    def get_section(
1788        self,
1789        position: int = 0,
1790        content: str | None = None,
1791    ) -> Element | None:
1792        """Return the section that matches the criteria.
1793
1794        Arguments:
1795
1796            position -- int
1797
1798            content -- str regex
1799
1800        Return: Element or None if not found
1801        """
1802        return self._filtered_element(
1803            "descendant::text:section", position, content=content
1804        )

Return the section that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Element or None if not found

def get_paragraphs( self, style: str | None = None, content: str | None = None) -> list[Element]:
1808    def get_paragraphs(
1809        self,
1810        style: str | None = None,
1811        content: str | None = None,
1812    ) -> list[Element]:
1813        """Return all the paragraphs that match the criteria.
1814
1815        Arguments:
1816
1817            style -- str
1818
1819            content -- str regex
1820
1821        Return: list of Paragraph
1822        """
1823        return self._filtered_elements(
1824            "descendant::text:p", text_style=style, content=content
1825        )

Return all the paragraphs that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Paragraph

def get_paragraph( self, position: int = 0, content: str | None = None) -> Element | None:
1827    def get_paragraph(
1828        self,
1829        position: int = 0,
1830        content: str | None = None,
1831    ) -> Element | None:
1832        """Return the paragraph that matches the criteria.
1833
1834        Arguments:
1835
1836            position -- int
1837
1838            content -- str regex
1839
1840        Return: Paragraph or None if not found
1841        """
1842        return self._filtered_element("descendant::text:p", position, content=content)

Return the paragraph that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Paragraph or None if not found

def get_spans( self, style: str | None = None, content: str | None = None) -> list[Element]:
1846    def get_spans(
1847        self,
1848        style: str | None = None,
1849        content: str | None = None,
1850    ) -> list[Element]:
1851        """Return all the spans that match the criteria.
1852
1853        Arguments:
1854
1855            style -- str
1856
1857            content -- str regex
1858
1859        Return: list of Span
1860        """
1861        return self._filtered_elements(
1862            "descendant::text:span", text_style=style, content=content
1863        )

Return all the spans that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Span

def get_span( self, position: int = 0, content: str | None = None) -> Element | None:
1865    def get_span(
1866        self,
1867        position: int = 0,
1868        content: str | None = None,
1869    ) -> Element | None:
1870        """Return the span that matches the criteria.
1871
1872        Arguments:
1873
1874            position -- int
1875
1876            content -- str regex
1877
1878        Return: Span or None if not found
1879        """
1880        return self._filtered_element(
1881            "descendant::text:span", position, content=content
1882        )

Return the span that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Span or None if not found

def get_headers( self, style: str | None = None, outline_level: str | None = None, content: str | None = None) -> list[Element]:
1886    def get_headers(
1887        self,
1888        style: str | None = None,
1889        outline_level: str | None = None,
1890        content: str | None = None,
1891    ) -> list[Element]:
1892        """Return all the Headers that match the criteria.
1893
1894        Arguments:
1895
1896            style -- str
1897
1898            content -- str regex
1899
1900        Return: list of Header
1901        """
1902        return self._filtered_elements(
1903            "descendant::text:h",
1904            text_style=style,
1905            outline_level=outline_level,
1906            content=content,
1907        )

Return all the Headers that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Header

def get_header( self, position: int = 0, outline_level: str | None = None, content: str | None = None) -> Element | None:
1909    def get_header(
1910        self,
1911        position: int = 0,
1912        outline_level: str | None = None,
1913        content: str | None = None,
1914    ) -> Element | None:
1915        """Return the Header that matches the criteria.
1916
1917        Arguments:
1918
1919            position -- int
1920
1921            content -- str regex
1922
1923        Return: Header or None if not found
1924        """
1925        return self._filtered_element(
1926            "descendant::text:h",
1927            position,
1928            outline_level=outline_level,
1929            content=content,
1930        )

Return the Header that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Header or None if not found

def get_lists( self, style: str | None = None, content: str | None = None) -> list[Element]:
1934    def get_lists(
1935        self,
1936        style: str | None = None,
1937        content: str | None = None,
1938    ) -> list[Element]:
1939        """Return all the lists that match the criteria.
1940
1941        Arguments:
1942
1943            style -- str
1944
1945            content -- str regex
1946
1947        Return: list of List
1948        """
1949        return self._filtered_elements(
1950            "descendant::text:list", text_style=style, content=content
1951        )

Return all the lists that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of List

def get_list( self, position: int = 0, content: str | None = None) -> Element | None:
1953    def get_list(
1954        self,
1955        position: int = 0,
1956        content: str | None = None,
1957    ) -> Element | None:
1958        """Return the list that matches the criteria.
1959
1960        Arguments:
1961
1962            position -- int
1963
1964            content -- str regex
1965
1966        Return: List or None if not found
1967        """
1968        return self._filtered_element(
1969            "descendant::text:list", position, content=content
1970        )

Return the list that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: List or None if not found

def get_frames( self, presentation_class: str | None = None, style: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> list[Element]:
1974    def get_frames(
1975        self,
1976        presentation_class: str | None = None,
1977        style: str | None = None,
1978        title: str | None = None,
1979        description: str | None = None,
1980        content: str | None = None,
1981    ) -> list[Element]:
1982        """Return all the frames that match the criteria.
1983
1984        Arguments:
1985
1986            presentation_class -- str
1987
1988            style -- str
1989
1990            title -- str regex
1991
1992            description -- str regex
1993
1994            content -- str regex
1995
1996        Return: list of Frame
1997        """
1998        return self._filtered_elements(
1999            "descendant::draw:frame",
2000            presentation_class=presentation_class,
2001            draw_style=style,
2002            svg_title=title,
2003            svg_desc=description,
2004            content=content,
2005        )

Return all the frames that match the criteria.

Arguments:

presentation_class -- str

style -- str

title -- str regex

description -- str regex

content -- str regex

Return: list of Frame

def get_frame( self, position: int = 0, name: str | None = None, presentation_class: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> Element | None:
2007    def get_frame(
2008        self,
2009        position: int = 0,
2010        name: str | None = None,
2011        presentation_class: str | None = None,
2012        title: str | None = None,
2013        description: str | None = None,
2014        content: str | None = None,
2015    ) -> Element | None:
2016        """Return the section that matches the criteria.
2017
2018        Arguments:
2019
2020            position -- int
2021
2022            name -- str
2023
2024            presentation_class -- str
2025
2026            title -- str regex
2027
2028            description -- str regex
2029
2030            content -- str regex
2031
2032        Return: Frame or None if not found
2033        """
2034        return self._filtered_element(
2035            "descendant::draw:frame",
2036            position,
2037            draw_name=name,
2038            presentation_class=presentation_class,
2039            svg_title=title,
2040            svg_desc=description,
2041            content=content,
2042        )

Return the section that matches the criteria.

Arguments:

position -- int

name -- str

presentation_class -- str

title -- str regex

description -- str regex

content -- str regex

Return: Frame or None if not found

def get_images( self, style: str | None = None, url: str | None = None, content: str | None = None) -> list[Element]:
2046    def get_images(
2047        self,
2048        style: str | None = None,
2049        url: str | None = None,
2050        content: str | None = None,
2051    ) -> list[Element]:
2052        """Return all the images matching the criteria.
2053
2054        Arguments:
2055
2056            style -- str
2057
2058            url -- str regex
2059
2060            content -- str regex
2061
2062        Return: list of Element
2063        """
2064        return self._filtered_elements(
2065            "descendant::draw:image", text_style=style, url=url, content=content
2066        )

Return all the images matching the criteria.

Arguments:

style -- str

url -- str regex

content -- str regex

Return: list of Element

def get_image( self, position: int = 0, name: str | None = None, url: str | None = None, content: str | None = None) -> Element | None:
2068    def get_image(
2069        self,
2070        position: int = 0,
2071        name: str | None = None,
2072        url: str | None = None,
2073        content: str | None = None,
2074    ) -> Element | None:
2075        """Return the image matching the criteria.
2076
2077        Arguments:
2078
2079            position -- int
2080
2081            name -- str
2082
2083            url -- str regex
2084
2085            content -- str regex
2086
2087        Return: Element or None if not found
2088        """
2089        # The frame is holding the name
2090        if name is not None:
2091            frame = self._filtered_element(
2092                "descendant::draw:frame", position, draw_name=name
2093            )
2094            if frame is None:
2095                return None
2096            # The name is supposedly unique
2097            return frame.get_element("draw:image")
2098        return self._filtered_element(
2099            "descendant::draw:image", position, url=url, content=content
2100        )

Return the image matching the criteria.

Arguments:

position -- int

name -- str

url -- str regex

content -- str regex

Return: Element or None if not found

def get_tables( self, style: str | None = None, content: str | None = None) -> list[Element]:
2104    def get_tables(
2105        self,
2106        style: str | None = None,
2107        content: str | None = None,
2108    ) -> list[Element]:
2109        """Return all the tables that match the criteria.
2110
2111        Arguments:
2112
2113            style -- str
2114
2115            content -- str regex
2116
2117        Return: list of Table
2118        """
2119        return self._filtered_elements(
2120            "descendant::table:table", table_style=style, content=content
2121        )

Return all the tables that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Table

def get_table( self, position: int = 0, name: str | None = None, content: str | None = None) -> Element | None:
2123    def get_table(
2124        self,
2125        position: int = 0,
2126        name: str | None = None,
2127        content: str | None = None,
2128    ) -> Element | None:
2129        """Return the table that matches the criteria.
2130
2131        Arguments:
2132
2133            position -- int
2134
2135            name -- str
2136
2137            content -- str regex
2138
2139        Return: Table or None if not found
2140        """
2141        if name is None and content is None:
2142            result = self._filtered_element("descendant::table:table", position)
2143        else:
2144            result = self._filtered_element(
2145                "descendant::table:table",
2146                position,
2147                table_name=name,
2148                content=content,
2149            )
2150        return result

Return the table that matches the criteria.

Arguments:

position -- int

name -- str

content -- str regex

Return: Table or None if not found

def get_named_ranges(self) -> list[Element]:
2154    def get_named_ranges(self) -> list[Element]:
2155        """Return all the tables named ranges.
2156
2157        Return: list of odf_named_range
2158        """
2159        named_ranges = self.get_elements(
2160            "descendant::table:named-expressions/table:named-range"
2161        )
2162        return named_ranges

Return all the tables named ranges.

Return: list of odf_named_range

def get_named_range(self, name: str) -> Element | None:
2164    def get_named_range(self, name: str) -> Element | None:
2165        """Return the named range of specified name, or None if not found.
2166
2167        Arguments:
2168
2169            name -- str
2170
2171        Return: NamedRange
2172        """
2173        named_range = self.get_elements(
2174            f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]'
2175        )
2176        if named_range:
2177            return named_range[0]
2178        else:
2179            return None

Return the named range of specified name, or None if not found.

Arguments:

name -- str

Return: NamedRange

def append_named_range(self, named_range: Element) -> None:
2181    def append_named_range(self, named_range: Element) -> None:
2182        """Append the named range to the spreadsheet, replacing existing named
2183        range of same name if any.
2184
2185        Arguments:
2186
2187            named_range --  NamedRange
2188        """
2189        if self.tag != "office:spreadsheet":
2190            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2191        named_expressions = self.get_element("table:named-expressions")
2192        if not named_expressions:
2193            named_expressions = Element.from_tag("table:named-expressions")
2194            self.append(named_expressions)
2195        # exists ?
2196        current = named_expressions.get_element(
2197            f'table:named-range[@table:name="{named_range.name}"][1]'  # type:ignore
2198        )
2199        if current:
2200            named_expressions.delete(current)
2201        named_expressions.append(named_range)

Append the named range to the spreadsheet, replacing existing named range of same name if any.

Arguments:

named_range --  NamedRange
def delete_named_range(self, name: str) -> None:
2203    def delete_named_range(self, name: str) -> None:
2204        """Delete the Named Range of specified name from the spreadsheet.
2205
2206        Arguments:
2207
2208            name -- str
2209        """
2210        if self.tag != "office:spreadsheet":
2211            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2212        named_range = self.get_named_range(name)
2213        if not named_range:
2214            return
2215        named_range.delete()
2216        named_expressions = self.get_element("table:named-expressions")
2217        if not named_expressions:
2218            return
2219        element = named_expressions.__element
2220        children = list(element.iterchildren())
2221        if not children:
2222            self.delete(named_expressions)

Delete the Named Range of specified name from the spreadsheet.

Arguments:

name -- str
def get_notes( self, note_class: str | None = None, content: str | None = None) -> list[Element]:
2226    def get_notes(
2227        self,
2228        note_class: str | None = None,
2229        content: str | None = None,
2230    ) -> list[Element]:
2231        """Return all the notes that match the criteria.
2232
2233        Arguments:
2234
2235            note_class -- 'footnote' or 'endnote'
2236
2237            content -- str regex
2238
2239        Return: list of Note
2240        """
2241        return self._filtered_elements(
2242            "descendant::text:note", note_class=note_class, content=content
2243        )

Return all the notes that match the criteria.

Arguments:

note_class -- 'footnote' or 'endnote'

content -- str regex

Return: list of Note

def get_note( self, position: int = 0, note_id: str | None = None, note_class: str | None = None, content: str | None = None) -> Element | None:
2245    def get_note(
2246        self,
2247        position: int = 0,
2248        note_id: str | None = None,
2249        note_class: str | None = None,
2250        content: str | None = None,
2251    ) -> Element | None:
2252        """Return the note that matches the criteria.
2253
2254        Arguments:
2255
2256            position -- int
2257
2258            note_id -- str
2259
2260            note_class -- 'footnote' or 'endnote'
2261
2262            content -- str regex
2263
2264        Return: Note or None if not found
2265        """
2266        return self._filtered_element(
2267            "descendant::text:note",
2268            position,
2269            text_id=note_id,
2270            note_class=note_class,
2271            content=content,
2272        )

Return the note that matches the criteria.

Arguments:

position -- int

note_id -- str

note_class -- 'footnote' or 'endnote'

content -- str regex

Return: Note or None if not found

def get_annotations( self, creator: str | None = None, start_date: datetime.datetime | None = None, end_date: datetime.datetime | None = None, content: str | None = None) -> list[Element]:
2276    def get_annotations(
2277        self,
2278        creator: str | None = None,
2279        start_date: datetime | None = None,
2280        end_date: datetime | None = None,
2281        content: str | None = None,
2282    ) -> list[Element]:
2283        """Return all the annotations that match the criteria.
2284
2285        Arguments:
2286
2287            creator -- str
2288
2289            start_date -- datetime instance
2290
2291            end_date --  datetime instance
2292
2293            content -- str regex
2294
2295        Return: list of Annotation
2296        """
2297        annotations = []
2298        for annotation in self._filtered_elements(
2299            "descendant::office:annotation", content=content
2300        ):
2301            if creator is not None and creator != annotation.dc_creator:
2302                continue
2303            date = annotation.dc_date
2304            if date is None:
2305                continue
2306            if start_date is not None and date < start_date:
2307                continue
2308            if end_date is not None and date >= end_date:
2309                continue
2310            annotations.append(annotation)
2311        return annotations

Return all the annotations that match the criteria.

Arguments:

creator -- str

start_date -- datetime instance

end_date --  datetime instance

content -- str regex

Return: list of Annotation

def get_annotation( self, position: int = 0, creator: str | None = None, start_date: datetime.datetime | None = None, end_date: datetime.datetime | None = None, content: str | None = None, name: str | None = None) -> Element | None:
2313    def get_annotation(
2314        self,
2315        position: int = 0,
2316        creator: str | None = None,
2317        start_date: datetime | None = None,
2318        end_date: datetime | None = None,
2319        content: str | None = None,
2320        name: str | None = None,
2321    ) -> Element | None:
2322        """Return the annotation that matches the criteria.
2323
2324        Arguments:
2325
2326            position -- int
2327
2328            creator -- str
2329
2330            start_date -- datetime instance
2331
2332            end_date -- datetime instance
2333
2334            content -- str regex
2335
2336            name -- str
2337
2338        Return: Annotation or None if not found
2339        """
2340        if name is not None:
2341            return self._filtered_element(
2342                "descendant::office:annotation", 0, office_name=name
2343            )
2344        annotations = self.get_annotations(
2345            creator=creator, start_date=start_date, end_date=end_date, content=content
2346        )
2347        if not annotations:
2348            return None
2349        try:
2350            return annotations[position]
2351        except IndexError:
2352            return None

Return the annotation that matches the criteria.

Arguments:

position -- int

creator -- str

start_date -- datetime instance

end_date -- datetime instance

content -- str regex

name -- str

Return: Annotation or None if not found

def get_annotation_ends(self) -> list[Element]:
2354    def get_annotation_ends(self) -> list[Element]:
2355        """Return all the annotation ends.
2356
2357        Return: list of Element
2358        """
2359        return self._filtered_elements("descendant::office:annotation-end")

Return all the annotation ends.

Return: list of Element

def get_annotation_end( self, position: int = 0, name: str | None = None) -> Element | None:
2361    def get_annotation_end(
2362        self,
2363        position: int = 0,
2364        name: str | None = None,
2365    ) -> Element | None:
2366        """Return the annotation end that matches the criteria.
2367
2368        Arguments:
2369
2370            position -- int
2371
2372            name -- str
2373
2374        Return: Element or None if not found
2375        """
2376        return self._filtered_element(
2377            "descendant::office:annotation-end", position, office_name=name
2378        )

Return the annotation end that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_office_names(self) -> list[str]:
2382    def get_office_names(self) -> list[str]:
2383        """Return all the used office:name tags values of the element.
2384
2385        Return: list of unique str
2386        """
2387        name_xpath_query = xpath_compile("//@office:name")
2388        response = name_xpath_query(self.__element)
2389        if not isinstance(response, list):
2390            return []
2391        return list({str(name) for name in response if name})

Return all the used office:name tags values of the element.

Return: list of unique str

def get_variable_decls(self) -> Element:
2395    def get_variable_decls(self) -> Element:
2396        """Return the container for variable declarations. Created if not
2397        found.
2398
2399        Return: Element
2400        """
2401        variable_decls = self.get_element("//text:variable-decls")
2402        if variable_decls is None:
2403            body = self.document_body
2404            if not body:
2405                raise ValueError("Empty document.body")
2406            body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD)
2407            variable_decls = body.get_element("//text:variable-decls")
2408
2409        return variable_decls  # type:ignore

Return the container for variable declarations. Created if not found.

Return: Element

def get_variable_decl_list(self) -> list[Element]:
2411    def get_variable_decl_list(self) -> list[Element]:
2412        """Return all the variable declarations.
2413
2414        Return: list of Element
2415        """
2416        return self._filtered_elements("descendant::text:variable-decl")

Return all the variable declarations.

Return: list of Element

def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2418    def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2419        """return the variable declaration for the given name.
2420
2421        Arguments:
2422
2423            name -- str
2424
2425            position -- int
2426
2427        return: Element or none if not found
2428        """
2429        return self._filtered_element(
2430            "descendant::text:variable-decl", position, text_name=name
2431        )

return the variable declaration for the given name.

Arguments:

name -- str

position -- int

return: Element or none if not found

def get_variable_sets(self, name: str | None = None) -> list[Element]:
2433    def get_variable_sets(self, name: str | None = None) -> list[Element]:
2434        """Return all the variable sets that match the criteria.
2435
2436        Arguments:
2437
2438            name -- str
2439
2440        Return: list of Element
2441        """
2442        return self._filtered_elements("descendant::text:variable-set", text_name=name)

Return all the variable sets that match the criteria.

Arguments:

name -- str

Return: list of Element

def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2444    def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2445        """Return the variable set for the given name (last one by default).
2446
2447        Arguments:
2448
2449            name -- str
2450
2451            position -- int
2452
2453        Return: Element or None if not found
2454        """
2455        return self._filtered_element(
2456            "descendant::text:variable-set", position, text_name=name
2457        )

Return the variable set for the given name (last one by default).

Arguments:

name -- str

position -- int

Return: Element or None if not found

def get_variable_set_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2459    def get_variable_set_value(
2460        self,
2461        name: str,
2462        value_type: str | None = None,
2463    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2464        """Return the last value of the given variable name.
2465
2466        Arguments:
2467
2468            name -- str
2469
2470            value_type -- 'boolean', 'currency', 'date', 'float',
2471                          'percentage', 'string', 'time' or automatic
2472
2473        Return: most appropriate Python type
2474        """
2475        variable_set = self.get_variable_set(name)
2476        if not variable_set:
2477            return None
2478        return variable_set.get_value(value_type)  # type: ignore

Return the last value of the given variable name.

Arguments:

name -- str

value_type -- 'boolean', 'currency', 'date', 'float',
              'percentage', 'string', 'time' or automatic

Return: most appropriate Python type

def get_user_field_decls(self) -> Element | None:
2482    def get_user_field_decls(self) -> Element | None:
2483        """Return the container for user field declarations. Created if not
2484        found.
2485
2486        Return: Element
2487        """
2488        user_field_decls = self.get_element("//text:user-field-decls")
2489        if user_field_decls is None:
2490            body = self.document_body
2491            if not body:
2492                raise ValueError("Empty document.body")
2493            body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD)
2494            user_field_decls = body.get_element("//text:user-field-decls")
2495
2496        return user_field_decls

Return the container for user field declarations. Created if not found.

Return: Element

def get_user_field_decl_list(self) -> list[Element]:
2498    def get_user_field_decl_list(self) -> list[Element]:
2499        """Return all the user field declarations.
2500
2501        Return: list of Element
2502        """
2503        return self._filtered_elements("descendant::text:user-field-decl")

Return all the user field declarations.

Return: list of Element

def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2505    def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2506        """return the user field declaration for the given name.
2507
2508        return: Element or none if not found
2509        """
2510        return self._filtered_element(
2511            "descendant::text:user-field-decl", position, text_name=name
2512        )

return the user field declaration for the given name.

return: Element or none if not found

def get_user_field_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2514    def get_user_field_value(
2515        self, name: str, value_type: str | None = None
2516    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2517        """Return the value of the given user field name.
2518
2519        Arguments:
2520
2521            name -- str
2522
2523            value_type -- 'boolean', 'currency', 'date', 'float',
2524                          'percentage', 'string', 'time' or automatic
2525
2526        Return: most appropriate Python type
2527        """
2528        user_field_decl = self.get_user_field_decl(name)
2529        if user_field_decl is None:
2530            return None
2531        return user_field_decl.get_value(value_type)  # type: ignore

Return the value of the given user field name.

Arguments:

name -- str

value_type -- 'boolean', 'currency', 'date', 'float',
              'percentage', 'string', 'time' or automatic

Return: most appropriate Python type

def get_user_defined_list(self) -> list[Element]:
2536    def get_user_defined_list(self) -> list[Element]:
2537        """Return all the user defined field declarations.
2538
2539        Return: list of Element
2540        """
2541        return self._filtered_elements("descendant::text:user-defined")

Return all the user defined field declarations.

Return: list of Element

def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2543    def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2544        """return the user defined declaration for the given name.
2545
2546        return: Element or none if not found
2547        """
2548        return self._filtered_element(
2549            "descendant::text:user-defined", position, text_name=name
2550        )

return the user defined declaration for the given name.

return: Element or none if not found

def get_user_defined_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2552    def get_user_defined_value(
2553        self, name: str, value_type: str | None = None
2554    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2555        """Return the value of the given user defined field name.
2556
2557        Arguments:
2558
2559            name -- str
2560
2561            value_type -- 'boolean', 'date', 'float',
2562                          'string', 'time' or automatic
2563
2564        Return: most appropriate Python type
2565        """
2566        user_defined = self.get_user_defined(name)
2567        if user_defined is None:
2568            return None
2569        return user_defined.get_value(value_type)  # type: ignore

Return the value of the given user defined field name.

Arguments:

name -- str

value_type -- 'boolean', 'date', 'float',
              'string', 'time' or automatic

Return: most appropriate Python type

def get_draw_pages( self, style: str | None = None, content: str | None = None) -> list[Element]:
2573    def get_draw_pages(
2574        self,
2575        style: str | None = None,
2576        content: str | None = None,
2577    ) -> list[Element]:
2578        """Return all the draw pages that match the criteria.
2579
2580        Arguments:
2581
2582            style -- str
2583
2584            content -- str regex
2585
2586        Return: list of DrawPage
2587        """
2588        return self._filtered_elements(
2589            "descendant::draw:page", draw_style=style, content=content
2590        )

Return all the draw pages that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of DrawPage

def get_draw_page( self, position: int = 0, name: str | None = None, content: str | None = None) -> Element | None:
2592    def get_draw_page(
2593        self,
2594        position: int = 0,
2595        name: str | None = None,
2596        content: str | None = None,
2597    ) -> Element | None:
2598        """Return the draw page that matches the criteria.
2599
2600        Arguments:
2601
2602            position -- int
2603
2604            name -- str
2605
2606            content -- str regex
2607
2608        Return: DrawPage or None if not found
2609        """
2610        return self._filtered_element(
2611            "descendant::draw:page", position, draw_name=name, content=content
2612        )

Return the draw page that matches the criteria.

Arguments:

position -- int

name -- str

content -- str regex

Return: DrawPage or None if not found

def get_bookmarks(self) -> list[Element]:
2680    def get_bookmarks(self) -> list[Element]:
2681        """Return all the bookmarks.
2682
2683        Return: list of Element
2684        """
2685        return self._filtered_elements("descendant::text:bookmark")

Return all the bookmarks.

Return: list of Element

def get_bookmark( self, position: int = 0, name: str | None = None) -> Element | None:
2687    def get_bookmark(
2688        self,
2689        position: int = 0,
2690        name: str | None = None,
2691    ) -> Element | None:
2692        """Return the bookmark that matches the criteria.
2693
2694        Arguments:
2695
2696            position -- int
2697
2698            name -- str
2699
2700        Return: Bookmark or None if not found
2701        """
2702        return self._filtered_element(
2703            "descendant::text:bookmark", position, text_name=name
2704        )

Return the bookmark that matches the criteria.

Arguments:

position -- int

name -- str

Return: Bookmark or None if not found

def get_bookmark_starts(self) -> list[Element]:
2706    def get_bookmark_starts(self) -> list[Element]:
2707        """Return all the bookmark starts.
2708
2709        Return: list of Element
2710        """
2711        return self._filtered_elements("descendant::text:bookmark-start")

Return all the bookmark starts.

Return: list of Element

def get_bookmark_start( self, position: int = 0, name: str | None = None) -> Element | None:
2713    def get_bookmark_start(
2714        self,
2715        position: int = 0,
2716        name: str | None = None,
2717    ) -> Element | None:
2718        """Return the bookmark start that matches the criteria.
2719
2720        Arguments:
2721
2722            position -- int
2723
2724            name -- str
2725
2726        Return: Element or None if not found
2727        """
2728        return self._filtered_element(
2729            "descendant::text:bookmark-start", position, text_name=name
2730        )

Return the bookmark start that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_bookmark_ends(self) -> list[Element]:
2732    def get_bookmark_ends(self) -> list[Element]:
2733        """Return all the bookmark ends.
2734
2735        Return: list of Element
2736        """
2737        return self._filtered_elements("descendant::text:bookmark-end")

Return all the bookmark ends.

Return: list of Element

def get_bookmark_end( self, position: int = 0, name: str | None = None) -> Element | None:
2739    def get_bookmark_end(
2740        self,
2741        position: int = 0,
2742        name: str | None = None,
2743    ) -> Element | None:
2744        """Return the bookmark end that matches the criteria.
2745
2746        Arguments:
2747
2748            position -- int
2749
2750            name -- str
2751
2752        Return: Element or None if not found
2753        """
2754        return self._filtered_element(
2755            "descendant::text:bookmark-end", position, text_name=name
2756        )

Return the bookmark end that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_marks_single(self) -> list[Element]:
2760    def get_reference_marks_single(self) -> list[Element]:
2761        """Return all the reference marks. Search only the tags
2762        text:reference-mark.
2763        Consider using : get_reference_marks()
2764
2765        Return: list of Element
2766        """
2767        return self._filtered_elements("descendant::text:reference-mark")

Return all the reference marks. Search only the tags text:reference-mark. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_single( self, position: int = 0, name: str | None = None) -> Element | None:
2769    def get_reference_mark_single(
2770        self,
2771        position: int = 0,
2772        name: str | None = None,
2773    ) -> Element | None:
2774        """Return the reference mark that matches the criteria. Search only the
2775        tags text:reference-mark.
2776        Consider using : get_reference_mark()
2777
2778        Arguments:
2779
2780            position -- int
2781
2782            name -- str
2783
2784        Return: Element or None if not found
2785        """
2786        return self._filtered_element(
2787            "descendant::text:reference-mark", position, text_name=name
2788        )

Return the reference mark that matches the criteria. Search only the tags text:reference-mark. Consider using : get_reference_mark()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_mark_starts(self) -> list[Element]:
2790    def get_reference_mark_starts(self) -> list[Element]:
2791        """Return all the reference mark starts. Search only the tags
2792        text:reference-mark-start.
2793        Consider using : get_reference_marks()
2794
2795        Return: list of Element
2796        """
2797        return self._filtered_elements("descendant::text:reference-mark-start")

Return all the reference mark starts. Search only the tags text:reference-mark-start. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_start( self, position: int = 0, name: str | None = None) -> Element | None:
2799    def get_reference_mark_start(
2800        self,
2801        position: int = 0,
2802        name: str | None = None,
2803    ) -> Element | None:
2804        """Return the reference mark start that matches the criteria. Search
2805        only the tags text:reference-mark-start.
2806        Consider using : get_reference_mark()
2807
2808        Arguments:
2809
2810            position -- int
2811
2812            name -- str
2813
2814        Return: Element or None if not found
2815        """
2816        return self._filtered_element(
2817            "descendant::text:reference-mark-start", position, text_name=name
2818        )

Return the reference mark start that matches the criteria. Search only the tags text:reference-mark-start. Consider using : get_reference_mark()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_mark_ends(self) -> list[Element]:
2820    def get_reference_mark_ends(self) -> list[Element]:
2821        """Return all the reference mark ends. Search only the tags
2822        text:reference-mark-end.
2823        Consider using : get_reference_marks()
2824
2825        Return: list of Element
2826        """
2827        return self._filtered_elements("descendant::text:reference-mark-end")

Return all the reference mark ends. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_end( self, position: int = 0, name: str | None = None) -> Element | None:
2829    def get_reference_mark_end(
2830        self,
2831        position: int = 0,
2832        name: str | None = None,
2833    ) -> Element | None:
2834        """Return the reference mark end that matches the criteria. Search only
2835        the tags text:reference-mark-end.
2836        Consider using : get_reference_marks()
2837
2838        Arguments:
2839
2840            position -- int
2841
2842            name -- str
2843
2844        Return: Element or None if not found
2845        """
2846        return self._filtered_element(
2847            "descendant::text:reference-mark-end", position, text_name=name
2848        )

Return the reference mark end that matches the criteria. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_marks(self) -> list[Element]:
2850    def get_reference_marks(self) -> list[Element]:
2851        """Return all the reference marks, either single position reference
2852        (text:reference-mark) or start of range reference
2853        (text:reference-mark-start).
2854
2855        Return: list of Element
2856        """
2857        return self._filtered_elements(
2858            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2859        )

Return all the reference marks, either single position reference (text:reference-mark) or start of range reference (text:reference-mark-start).

Return: list of Element

def get_reference_mark( self, position: int = 0, name: str | None = None) -> Element | None:
2861    def get_reference_mark(
2862        self,
2863        position: int = 0,
2864        name: str | None = None,
2865    ) -> Element | None:
2866        """Return the reference mark that match the criteria. Either single
2867        position reference mark (text:reference-mark) or start of range
2868        reference (text:reference-mark-start).
2869
2870        Arguments:
2871
2872            position -- int
2873
2874            name -- str
2875
2876        Return: Element or None if not found
2877        """
2878        if name:
2879            request = (
2880                f"descendant::text:reference-mark-start"
2881                f'[@text:name="{name}"] '
2882                f"| descendant::text:reference-mark"
2883                f'[@text:name="{name}"]'
2884            )
2885            return self._filtered_element(request, position=0)
2886        request = (
2887            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2888        )
2889        return self._filtered_element(request, position)

Return the reference mark that match the criteria. Either single position reference mark (text:reference-mark) or start of range reference (text:reference-mark-start).

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_references(self, name: str | None = None) -> list[Element]:
2891    def get_references(self, name: str | None = None) -> list[Element]:
2892        """Return all the references (text:reference-ref). If name is
2893        provided, returns the references of that name.
2894
2895        Return: list of Element
2896
2897        Arguments:
2898
2899            name -- str or None
2900        """
2901        if name is None:
2902            return self._filtered_elements("descendant::text:reference-ref")
2903        request = f'descendant::text:reference-ref[@text:ref-name="{name}"]'
2904        return self._filtered_elements(request)

Return all the references (text:reference-ref). If name is provided, returns the references of that name.

Return: list of Element

Arguments:

name -- str or None
def get_draw_groups( self, title: str | None = None, description: str | None = None, content: str | None = None) -> list[Element]:
2910    def get_draw_groups(
2911        self,
2912        title: str | None = None,
2913        description: str | None = None,
2914        content: str | None = None,
2915    ) -> list[Element]:
2916        return self._filtered_elements(
2917            "descendant::draw:g",
2918            svg_title=title,
2919            svg_desc=description,
2920            content=content,
2921        )
def get_draw_group( self, position: int = 0, name: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> Element | None:
2923    def get_draw_group(
2924        self,
2925        position: int = 0,
2926        name: str | None = None,
2927        title: str | None = None,
2928        description: str | None = None,
2929        content: str | None = None,
2930    ) -> Element | None:
2931        return self._filtered_element(
2932            "descendant::draw:g",
2933            position,
2934            draw_name=name,
2935            svg_title=title,
2936            svg_desc=description,
2937            content=content,
2938        )
def get_draw_lines( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
2942    def get_draw_lines(
2943        self,
2944        draw_style: str | None = None,
2945        draw_text_style: str | None = None,
2946        content: str | None = None,
2947    ) -> list[Element]:
2948        """Return all the draw lines that match the criteria.
2949
2950        Arguments:
2951
2952            draw_style -- str
2953
2954            draw_text_style -- str
2955
2956            content -- str regex
2957
2958        Return: list of odf_shape
2959        """
2960        return self._filtered_elements(
2961            "descendant::draw:line",
2962            draw_style=draw_style,
2963            draw_text_style=draw_text_style,
2964            content=content,
2965        )

Return all the draw lines that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_line( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
2967    def get_draw_line(
2968        self,
2969        position: int = 0,
2970        id: str | None = None,  # noqa:A002
2971        content: str | None = None,
2972    ) -> Element | None:
2973        """Return the draw line that matches the criteria.
2974
2975        Arguments:
2976
2977            position -- int
2978
2979            id -- str
2980
2981            content -- str regex
2982
2983        Return: odf_shape or None if not found
2984        """
2985        return self._filtered_element(
2986            "descendant::draw:line", position, draw_id=id, content=content
2987        )

Return the draw line that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_rectangles( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
2991    def get_draw_rectangles(
2992        self,
2993        draw_style: str | None = None,
2994        draw_text_style: str | None = None,
2995        content: str | None = None,
2996    ) -> list[Element]:
2997        """Return all the draw rectangles that match the criteria.
2998
2999        Arguments:
3000
3001            draw_style -- str
3002
3003            draw_text_style -- str
3004
3005            content -- str regex
3006
3007        Return: list of odf_shape
3008        """
3009        return self._filtered_elements(
3010            "descendant::draw:rect",
3011            draw_style=draw_style,
3012            draw_text_style=draw_text_style,
3013            content=content,
3014        )

Return all the draw rectangles that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_rectangle( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3016    def get_draw_rectangle(
3017        self,
3018        position: int = 0,
3019        id: str | None = None,  # noqa:A002
3020        content: str | None = None,
3021    ) -> Element | None:
3022        """Return the draw rectangle that matches the criteria.
3023
3024        Arguments:
3025
3026            position -- int
3027
3028            id -- str
3029
3030            content -- str regex
3031
3032        Return: odf_shape or None if not found
3033        """
3034        return self._filtered_element(
3035            "descendant::draw:rect", position, draw_id=id, content=content
3036        )

Return the draw rectangle that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_ellipses( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
3040    def get_draw_ellipses(
3041        self,
3042        draw_style: str | None = None,
3043        draw_text_style: str | None = None,
3044        content: str | None = None,
3045    ) -> list[Element]:
3046        """Return all the draw ellipses that match the criteria.
3047
3048        Arguments:
3049
3050            draw_style -- str
3051
3052            draw_text_style -- str
3053
3054            content -- str regex
3055
3056        Return: list of odf_shape
3057        """
3058        return self._filtered_elements(
3059            "descendant::draw:ellipse",
3060            draw_style=draw_style,
3061            draw_text_style=draw_text_style,
3062            content=content,
3063        )

Return all the draw ellipses that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_ellipse( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3065    def get_draw_ellipse(
3066        self,
3067        position: int = 0,
3068        id: str | None = None,  # noqa:A002
3069        content: str | None = None,
3070    ) -> Element | None:
3071        """Return the draw ellipse that matches the criteria.
3072
3073        Arguments:
3074
3075            position -- int
3076
3077            id -- str
3078
3079            content -- str regex
3080
3081        Return: odf_shape or None if not found
3082        """
3083        return self._filtered_element(
3084            "descendant::draw:ellipse", position, draw_id=id, content=content
3085        )

Return the draw ellipse that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_connectors( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
3089    def get_draw_connectors(
3090        self,
3091        draw_style: str | None = None,
3092        draw_text_style: str | None = None,
3093        content: str | None = None,
3094    ) -> list[Element]:
3095        """Return all the draw connectors that match the criteria.
3096
3097        Arguments:
3098
3099            draw_style -- str
3100
3101            draw_text_style -- str
3102
3103            content -- str regex
3104
3105        Return: list of odf_shape
3106        """
3107        return self._filtered_elements(
3108            "descendant::draw:connector",
3109            draw_style=draw_style,
3110            draw_text_style=draw_text_style,
3111            content=content,
3112        )

Return all the draw connectors that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_connector( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3114    def get_draw_connector(
3115        self,
3116        position: int = 0,
3117        id: str | None = None,  # noqa:A002
3118        content: str | None = None,
3119    ) -> Element | None:
3120        """Return the draw connector that matches the criteria.
3121
3122        Arguments:
3123
3124            position -- int
3125
3126            id -- str
3127
3128            content -- str regex
3129
3130        Return: odf_shape or None if not found
3131        """
3132        return self._filtered_element(
3133            "descendant::draw:connector", position, draw_id=id, content=content
3134        )

Return the draw connector that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_orphan_draw_connectors(self) -> list[Element]:
3136    def get_orphan_draw_connectors(self) -> list[Element]:
3137        """Return a list of connectors which don't have any shape connected
3138        to them.
3139        """
3140        connectors = []
3141        for connector in self.get_draw_connectors():
3142            start_shape = connector.get_attribute("draw:start-shape")
3143            end_shape = connector.get_attribute("draw:end-shape")
3144            if start_shape is None and end_shape is None:
3145                connectors.append(connector)
3146        return connectors

Return a list of connectors which don't have any shape connected to them.

def get_tracked_changes(self) -> Element | None:
3150    def get_tracked_changes(self) -> Element | None:
3151        """Return the tracked-changes part in the text body."""
3152        return self.get_element("//text:tracked-changes")

Return the tracked-changes part in the text body.

def get_changes_ids(self) -> list[Element | Text]:
3154    def get_changes_ids(self) -> list[Element | Text]:
3155        """Return a list of ids that refers to a change region in the tracked
3156        changes list.
3157        """
3158        # Insertion changes
3159        xpath_query = "descendant::text:change-start/@text:change-id"
3160        # Deletion changes
3161        xpath_query += " | descendant::text:change/@text:change-id"
3162        return self.xpath(xpath_query)

Return a list of ids that refers to a change region in the tracked changes list.

def get_text_change_deletions(self) -> list[Element]:
3164    def get_text_change_deletions(self) -> list[Element]:
3165        """Return all the text changes of deletion kind: the tags text:change.
3166        Consider using : get_text_changes()
3167
3168        Return: list of Element
3169        """
3170        return self._filtered_elements("descendant::text:text:change")

Return all the text changes of deletion kind: the tags text:change. Consider using : get_text_changes()

Return: list of Element

def get_text_change_deletion( self, position: int = 0, idx: str | None = None) -> Element | None:
3172    def get_text_change_deletion(
3173        self,
3174        position: int = 0,
3175        idx: str | None = None,
3176    ) -> Element | None:
3177        """Return the text change of deletion kind that matches the criteria.
3178        Search only for the tags text:change.
3179        Consider using : get_text_change()
3180
3181        Arguments:
3182
3183            position -- int
3184
3185            idx -- str
3186
3187        Return: Element or None if not found
3188        """
3189        return self._filtered_element(
3190            "descendant::text:change", position, change_id=idx
3191        )

Return the text change of deletion kind that matches the criteria. Search only for the tags text:change. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_change_starts(self) -> list[Element]:
3193    def get_text_change_starts(self) -> list[Element]:
3194        """Return all the text change-start. Search only for the tags
3195        text:change-start.
3196        Consider using : get_text_changes()
3197
3198        Return: list of Element
3199        """
3200        return self._filtered_elements("descendant::text:change-start")

Return all the text change-start. Search only for the tags text:change-start. Consider using : get_text_changes()

Return: list of Element

def get_text_change_start( self, position: int = 0, idx: str | None = None) -> Element | None:
3202    def get_text_change_start(
3203        self,
3204        position: int = 0,
3205        idx: str | None = None,
3206    ) -> Element | None:
3207        """Return the text change-start that matches the criteria. Search
3208        only the tags text:change-start.
3209        Consider using : get_text_change()
3210
3211        Arguments:
3212
3213            position -- int
3214
3215            idx -- str
3216
3217        Return: Element or None if not found
3218        """
3219        return self._filtered_element(
3220            "descendant::text:change-start", position, change_id=idx
3221        )

Return the text change-start that matches the criteria. Search only the tags text:change-start. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_change_ends(self) -> list[Element]:
3223    def get_text_change_ends(self) -> list[Element]:
3224        """Return all the text change-end. Search only the tags
3225        text:change-end.
3226        Consider using : get_text_changes()
3227
3228        Return: list of Element
3229        """
3230        return self._filtered_elements("descendant::text:change-end")

Return all the text change-end. Search only the tags text:change-end. Consider using : get_text_changes()

Return: list of Element

def get_text_change_end( self, position: int = 0, idx: str | None = None) -> Element | None:
3232    def get_text_change_end(
3233        self,
3234        position: int = 0,
3235        idx: str | None = None,
3236    ) -> Element | None:
3237        """Return the text change-end that matches the criteria. Search only
3238        the tags text:change-end.
3239        Consider using : get_text_change()
3240
3241        Arguments:
3242
3243            position -- int
3244
3245            idx -- str
3246
3247        Return: Element or None if not found
3248        """
3249        return self._filtered_element(
3250            "descendant::text:change-end", position, change_id=idx
3251        )

Return the text change-end that matches the criteria. Search only the tags text:change-end. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_changes(self) -> list[Element]:
3253    def get_text_changes(self) -> list[Element]:
3254        """Return all the text changes, either single deletion
3255        (text:change) or start of range of changes (text:change-start).
3256
3257        Return: list of Element
3258        """
3259        request = "descendant::text:change-start | descendant::text:change"
3260        return self._filtered_elements(request)

Return all the text changes, either single deletion (text:change) or start of range of changes (text:change-start).

Return: list of Element

def get_text_change( self, position: int = 0, idx: str | None = None) -> Element | None:
3262    def get_text_change(
3263        self,
3264        position: int = 0,
3265        idx: str | None = None,
3266    ) -> Element | None:
3267        """Return the text change that matches the criteria. Either single
3268        deletion (text:change) or start of range of changes (text:change-start).
3269        position : index of the element to retrieve if several matches, default
3270        is 0.
3271        idx : change-id of the element.
3272
3273        Arguments:
3274
3275            position -- int
3276
3277            idx -- str
3278
3279        Return: Element or None if not found
3280        """
3281        if idx:
3282            request = (
3283                f'descendant::text:change-start[@text:change-id="{idx}"] '
3284                f'| descendant::text:change[@text:change-id="{idx}"]'
3285            )
3286            return self._filtered_element(request, 0)
3287        request = "descendant::text:change-start | descendant::text:change"
3288        return self._filtered_element(request, position)

Return the text change that matches the criteria. Either single deletion (text:change) or start of range of changes (text:change-start). position : index of the element to retrieve if several matches, default is 0. idx : change-id of the element.

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_tocs(self) -> list[Element]:
3292    def get_tocs(self) -> list[Element]:
3293        """Return all the tables of contents.
3294
3295        Return: list of odf_toc
3296        """
3297        return self._filtered_elements("text:table-of-content")

Return all the tables of contents.

Return: list of odf_toc

def get_toc( self, position: int = 0, content: str | None = None) -> Element | None:
3299    def get_toc(
3300        self,
3301        position: int = 0,
3302        content: str | None = None,
3303    ) -> Element | None:
3304        """Return the table of contents that matches the criteria.
3305
3306        Arguments:
3307
3308            position -- int
3309
3310            content -- str regex
3311
3312        Return: odf_toc or None if not found
3313        """
3314        return self._filtered_element(
3315            "text:table-of-content", position, content=content
3316        )

Return the table of contents that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: odf_toc or None if not found

def get_styles(self, family: str | None = None) -> list[Element]:
3338    def get_styles(self, family: str | None = None) -> list[Element]:
3339        # Both common and default styles
3340        tagname = self._get_style_tagname(family)
3341        return self._filtered_elements(tagname, family=family)
def get_style( self, family: str, name_or_element: str | Element | None = None, display_name: str | None = None) -> Element | None:
3343    def get_style(
3344        self,
3345        family: str,
3346        name_or_element: str | Element | None = None,
3347        display_name: str | None = None,
3348    ) -> Element | None:
3349        """Return the style uniquely identified by the family/name pair. If
3350        the argument is already a style object, it will return it.
3351
3352        If the name is not the internal name but the name you gave in the
3353        desktop application, use display_name instead.
3354
3355        Arguments:
3356
3357            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
3358                      'number'
3359
3360            name_or_element -- str or Style
3361
3362            display_name -- str
3363
3364        Return: odf_style or None if not found
3365        """
3366        if isinstance(name_or_element, Element):
3367            name = self.get_attribute("style:name")
3368            if name is not None:
3369                return name_or_element
3370            else:
3371                raise ValueError(f"Not a odf_style ? {name_or_element!r}")
3372        style_name = name_or_element
3373        is_default = not (style_name or display_name)
3374        tagname = self._get_style_tagname(family, is_default=is_default)
3375        # famattr became None if no "style:family" attribute
3376        if family:
3377            return self._filtered_element(
3378                tagname,
3379                0,
3380                style_name=style_name,
3381                display_name=display_name,
3382                family=family,
3383            )
3384        else:
3385            return self._filtered_element(
3386                tagname,
3387                0,
3388                draw_name=style_name or display_name,
3389                family=family,
3390            )

Return the style uniquely identified by the family/name pair. If the argument is already a style object, it will return it.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text', 'graphic', 'table', 'list',
          'number'

name_or_element -- str or Style

display_name -- str

Return: odf_style or None if not found

class ElementTyped(odfdo.Element):
 35class ElementTyped(Element):
 36    def set_value_and_type(  # noqa: C901
 37        self,
 38        value: Any,
 39        value_type: str | None = None,
 40        text: str | None = None,
 41        currency: str | None = None,
 42    ) -> str | None:
 43        # Remove possible previous value and type
 44        for name in (
 45            "office:value-type",
 46            "office:boolean-value",
 47            "office:value",
 48            "office:date-value",
 49            "office:string-value",
 50            "office:time-value",
 51            "table:formula",
 52            "office:currency",
 53            "calcext:value-type",
 54            "loext:value-type",
 55        ):
 56            with contextlib.suppress(KeyError):
 57                self.del_attribute(name)
 58        if isinstance(value, bytes):
 59            value = bytes_to_str(value)
 60        if isinstance(value_type, bytes):
 61            value_type = bytes_to_str(value_type)
 62        if isinstance(text, bytes):
 63            text = bytes_to_str(text)
 64        if isinstance(currency, bytes):
 65            currency = bytes_to_str(currency)
 66        if value is None:
 67            self._erase_text_content()
 68            return text
 69        if isinstance(value, bool):
 70            if value_type is None:
 71                value_type = "boolean"
 72            if text is None:
 73                text = "true" if value else "false"
 74            value = Boolean.encode(value)
 75        elif isinstance(value, (int, float, Decimal)):
 76            if value_type == "percentage":
 77                text = "%d %%" % int(value * 100)
 78            if value_type is None:
 79                value_type = "float"
 80            if text is None:
 81                text = str(value)
 82            value = str(value)
 83        elif isinstance(value, datetime):
 84            if value_type is None:
 85                value_type = "date"
 86            if text is None:
 87                text = str(DateTime.encode(value))
 88            value = DateTime.encode(value)
 89        elif isinstance(value, date):
 90            if value_type is None:
 91                value_type = "date"
 92            if text is None:
 93                text = str(Date.encode(value))
 94            value = Date.encode(value)
 95        elif isinstance(value, str):
 96            if value_type is None:
 97                value_type = "string"
 98            if text is None:
 99                text = value
100        elif isinstance(value, timedelta):
101            if value_type is None:
102                value_type = "time"
103            if text is None:
104                text = str(Duration.encode(value))
105            value = Duration.encode(value)
106        elif value is not None:
107            raise TypeError(f"Type unknown: '{value!r}'")
108
109        if value_type is not None:
110            self.set_attribute("office:value-type", value_type)
111            self.set_attribute("calcext:value-type", value_type)
112        if value_type == "boolean":
113            self.set_attribute("office:boolean-value", value)
114        elif value_type == "currency":
115            self.set_attribute("office:value", value)
116            self.set_attribute("office:currency", currency)
117        elif value_type == "date":
118            self.set_attribute("office:date-value", value)
119        elif value_type in ("float", "percentage"):
120            self.set_attribute("office:value", value)
121            self.set_attribute("calcext:value", value)
122        elif value_type == "string":
123            self.set_attribute("office:string-value", value)
124        elif value_type == "time":
125            self.set_attribute("office:time-value", value)
126
127        return text
128
129    def _get_typed_value(  # noqa: C901
130        self,
131        value_type: str | None = None,
132        try_get_text: bool = True,
133    ) -> tuple[Any, str | None]:
134        """Return Python typed value.
135
136        Only for "with office:value-type" elements, not for meta fields."""
137        value: Decimal | str | bool | None = None
138        if value_type is None:
139            read_value_type = self.get_attribute("office:value-type")
140            if isinstance(read_value_type, bool):
141                raise TypeError(
142                    f'Wrong type for "office:value-type": {type(read_value_type)}'
143                )
144            value_type = read_value_type
145        # value_type = to_str(value_type)
146        if value_type == "boolean":
147            value = self.get_attribute("office:boolean-value")
148            return (value, value_type)
149        if value_type in {"float", "percentage", "currency"}:
150            read_number = self.get_attribute("office:value")
151            if not isinstance(read_number, (Decimal, str)):
152                raise TypeError(f'Wrong type for "office:value": {type(read_number)}')
153            value = Decimal(read_number)
154            # Return 3 instead of 3.0 if possible
155            if int(value) == value:
156                return (int(value), value_type)
157            return (value, value_type)
158        if value_type == "date":
159            read_attribute = self.get_attribute("office:date-value")
160            if not isinstance(read_attribute, str):
161                raise TypeError(
162                    f'Wrong type for "office:date-value": {type(read_attribute)}'
163                )
164            if "T" in read_attribute:
165                return (DateTime.decode(read_attribute), value_type)
166            return (Date.decode(read_attribute), value_type)
167        if value_type == "string":
168            value = self.get_attribute("office:string-value")
169            if value is not None:
170                return (str(value), value_type)
171            if try_get_text:
172                list_value = [
173                    para.text_recursive for para in self.get_elements("text:p")
174                ]
175                if list_value:
176                    return ("\n".join(list_value), value_type)
177            return (None, value_type)
178        if value_type == "time":
179            read_value = self.get_attribute("office:time-value")
180            if not isinstance(read_value, str):
181                raise TypeError(
182                    f'Wrong type for "office:time-value": {type(read_value)}'
183                )
184            time_value = Duration.decode(read_value)
185            return (time_value, value_type)
186        if value_type is None:
187            return (None, None)
188        raise ValueError(f'Unexpected value type: "{value_type}"')
189
190    def get_value(
191        self,
192        value_type: str | None = None,
193        try_get_text: bool = True,
194        get_type: bool = False,
195    ) -> Any | tuple[Any, str]:
196        """Return Python typed value.
197
198        Only for "with office:value-type" elements, not for meta fields."""
199        if get_type:
200            return self._get_typed_value(
201                value_type=value_type,
202                try_get_text=try_get_text,
203            )
204        return self._get_typed_value(
205            value_type=value_type,
206            try_get_text=try_get_text,
207        )[0]

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

def set_value_and_type( self, value: Any, value_type: str | None = None, text: str | None = None, currency: str | None = None) -> str | None:
 36    def set_value_and_type(  # noqa: C901
 37        self,
 38        value: Any,
 39        value_type: str | None = None,
 40        text: str | None = None,
 41        currency: str | None = None,
 42    ) -> str | None:
 43        # Remove possible previous value and type
 44        for name in (
 45            "office:value-type",
 46            "office:boolean-value",
 47            "office:value",
 48            "office:date-value",
 49            "office:string-value",
 50            "office:time-value",
 51            "table:formula",
 52            "office:currency",
 53            "calcext:value-type",
 54            "loext:value-type",
 55        ):
 56            with contextlib.suppress(KeyError):
 57                self.del_attribute(name)
 58        if isinstance(value, bytes):
 59            value = bytes_to_str(value)
 60        if isinstance(value_type, bytes):
 61            value_type = bytes_to_str(value_type)
 62        if isinstance(text, bytes):
 63            text = bytes_to_str(text)
 64        if isinstance(currency, bytes):
 65            currency = bytes_to_str(currency)
 66        if value is None:
 67            self._erase_text_content()
 68            return text
 69        if isinstance(value, bool):
 70            if value_type is None:
 71                value_type = "boolean"
 72            if text is None:
 73                text = "true" if value else "false"
 74            value = Boolean.encode(value)
 75        elif isinstance(value, (int, float, Decimal)):
 76            if value_type == "percentage":
 77                text = "%d %%" % int(value * 100)
 78            if value_type is None:
 79                value_type = "float"
 80            if text is None:
 81                text = str(value)
 82            value = str(value)
 83        elif isinstance(value, datetime):
 84            if value_type is None:
 85                value_type = "date"
 86            if text is None:
 87                text = str(DateTime.encode(value))
 88            value = DateTime.encode(value)
 89        elif isinstance(value, date):
 90            if value_type is None:
 91                value_type = "date"
 92            if text is None:
 93                text = str(Date.encode(value))
 94            value = Date.encode(value)
 95        elif isinstance(value, str):
 96            if value_type is None:
 97                value_type = "string"
 98            if text is None:
 99                text = value
100        elif isinstance(value, timedelta):
101            if value_type is None:
102                value_type = "time"
103            if text is None:
104                text = str(Duration.encode(value))
105            value = Duration.encode(value)
106        elif value is not None:
107            raise TypeError(f"Type unknown: '{value!r}'")
108
109        if value_type is not None:
110            self.set_attribute("office:value-type", value_type)
111            self.set_attribute("calcext:value-type", value_type)
112        if value_type == "boolean":
113            self.set_attribute("office:boolean-value", value)
114        elif value_type == "currency":
115            self.set_attribute("office:value", value)
116            self.set_attribute("office:currency", currency)
117        elif value_type == "date":
118            self.set_attribute("office:date-value", value)
119        elif value_type in ("float", "percentage"):
120            self.set_attribute("office:value", value)
121            self.set_attribute("calcext:value", value)
122        elif value_type == "string":
123            self.set_attribute("office:string-value", value)
124        elif value_type == "time":
125            self.set_attribute("office:time-value", value)
126
127        return text
def get_value( self, value_type: str | None = None, try_get_text: bool = True, get_type: bool = False) -> typing.Any | tuple[typing.Any, str]:
190    def get_value(
191        self,
192        value_type: str | None = None,
193        try_get_text: bool = True,
194        get_type: bool = False,
195    ) -> Any | tuple[Any, str]:
196        """Return Python typed value.
197
198        Only for "with office:value-type" elements, not for meta fields."""
199        if get_type:
200            return self._get_typed_value(
201                value_type=value_type,
202                try_get_text=try_get_text,
203            )
204        return self._get_typed_value(
205            value_type=value_type,
206            try_get_text=try_get_text,
207        )[0]

Return Python typed value.

Only for "with office:value-type" elements, not for meta fields.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class EllipseShape(odfdo.shapes.ShapeBase):
194class EllipseShape(ShapeBase):
195    """Create a ellipse shape.
196
197    Arguments:
198
199        style -- str
200
201        text_style -- str
202
203        draw_id -- str
204
205        layer -- str
206
207        position -- (str, str)
208
209        size -- (str, str)
210
211    """
212
213    _tag = "draw:ellipse"
214    _properties: tuple[PropDef, ...] = ()
215
216    def __init__(
217        self,
218        style: str | None = None,
219        text_style: str | None = None,
220        draw_id: str | None = None,
221        layer: str | None = None,
222        position: tuple | None = None,
223        size: tuple | None = None,
224        **kwargs: Any,
225    ) -> None:
226        kwargs.update(
227            {
228                "style": style,
229                "text_style": text_style,
230                "draw_id": draw_id,
231                "layer": layer,
232                "size": size,
233                "position": position,
234            }
235        )
236        super().__init__(**kwargs)

Create a ellipse shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

position -- (str, str)

size -- (str, str)
EllipseShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, position: tuple | None = None, size: tuple | None = None, **kwargs: Any)
216    def __init__(
217        self,
218        style: str | None = None,
219        text_style: str | None = None,
220        draw_id: str | None = None,
221        layer: str | None = None,
222        position: tuple | None = None,
223        size: tuple | None = None,
224        **kwargs: Any,
225    ) -> None:
226        kwargs.update(
227            {
228                "style": style,
229                "text_style": text_style,
230                "draw_id": draw_id,
231                "layer": layer,
232                "size": size,
233                "position": position,
234            }
235        )
236        super().__init__(**kwargs)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
FIRST_CHILD = 0
class Frame(odfdo.Element, odfdo.frame.AnchorMix, odfdo.frame.PosMix, odfdo.frame.ZMix, odfdo.frame.SizeMix):
168class Frame(Element, AnchorMix, PosMix, ZMix, SizeMix):
169    """ODF Frame "draw:frame"
170
171    Frames are not useful by themselves. You should consider calling
172    Frame.image_frame() or Frame.text_frame directly.
173    """
174
175    _tag = "draw:frame"
176    _properties = (
177        PropDef("name", "draw:name"),
178        PropDef("draw_id", "draw:id"),
179        PropDef("width", "svg:width"),
180        PropDef("height", "svg:height"),
181        PropDef("style", "draw:style-name"),
182        PropDef("pos_x", "svg:x"),
183        PropDef("pos_y", "svg:y"),
184        PropDef("presentation_class", "presentation:class"),
185        PropDef("layer", "draw:layer"),
186        PropDef("presentation_style", "presentation:style-name"),
187    )
188
189    def __init__(  # noqa:  C901
190        self,
191        name: str | None = None,
192        draw_id: str | None = None,
193        style: str | None = None,
194        position: tuple | None = None,
195        size: tuple = ("1cm", "1cm"),
196        z_index: int = 0,
197        presentation_class: str | None = None,
198        anchor_type: str | None = None,
199        anchor_page: int | None = None,
200        layer: str | None = None,
201        presentation_style: str | None = None,
202        **kwargs: Any,
203    ) -> None:
204        """Create a frame element of the given size. Position is relative to the
205        context the frame is inserted in. If positioned by page, give the page
206        number and the x, y position.
207
208        Size is a (width, height) tuple and position is a (left, top) tuple; items
209        are strings including the unit, e.g. ('10cm', '15cm').
210
211        Frames are not useful by themselves. You should consider calling:
212            Frame.image_frame()
213        or
214            Frame.text_frame()
215
216
217        Arguments:
218
219            name -- str
220
221            draw_id -- str
222
223            style -- str
224
225            position -- (str, str)
226
227            size -- (str, str)
228
229            z_index -- int (default 0)
230
231            presentation_class -- str
232
233            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
234
235            anchor_page -- int, page number is anchor_type is 'page'
236
237            layer -- str
238
239            presentation_style -- str
240        """
241        super().__init__(**kwargs)
242        if self._do_init:
243            self.size = size
244            self.z_index = z_index
245            if name:
246                self.name = name
247            if draw_id is not None:
248                self.draw_id = draw_id
249            if style is not None:
250                self.style = style
251            if position is not None:
252                self.position = position
253            if presentation_class is not None:
254                self.presentation_class = presentation_class
255            if anchor_type:
256                self.anchor_type = anchor_type
257            if position and not anchor_type:
258                self.anchor_type = "paragraph"
259            if anchor_page is not None:
260                self.anchor_page = anchor_page
261            if layer is not None:
262                self.layer = layer
263            if presentation_style is not None:
264                self.presentation_style = presentation_style
265
266    @classmethod
267    def image_frame(
268        cls,
269        image: Element | str,
270        text: str | None = None,
271        name: str | None = None,
272        draw_id: str | None = None,
273        style: str | None = None,
274        position: tuple | None = None,
275        size: tuple = ("1cm", "1cm"),
276        z_index: int = 0,
277        presentation_class: str | None = None,
278        anchor_type: str | None = None,
279        anchor_page: int | None = None,
280        layer: str | None = None,
281        presentation_style: str | None = None,
282        **kwargs: Any,
283    ) -> Element:
284        """Create a ready-to-use image, since image must be embedded in a
285        frame.
286
287        The optionnal text will appear above the image.
288
289        Arguments:
290
291            image -- DrawImage or str, DrawImage element or URL of the image
292
293            text -- str, text for the image
294
295            See Frame() initialization for the other arguments
296
297        Return: Frame
298        """
299        frame = cls(
300            name=name,
301            draw_id=draw_id,
302            style=style,
303            position=position,
304            size=size,
305            z_index=z_index,
306            presentation_class=presentation_class,
307            anchor_type=anchor_type,
308            anchor_page=anchor_page,
309            layer=layer,
310            presentation_style=presentation_style,
311            **kwargs,
312        )
313        image_element = frame.set_image(image)
314        if text:
315            image_element.text_content = text
316        return frame
317
318    @classmethod
319    def text_frame(
320        cls,
321        text_or_element: Iterable[Element] | Element | str,
322        text_style: str | None = None,
323        name: str | None = None,
324        draw_id: str | None = None,
325        style: str | None = None,
326        position: tuple | None = None,
327        size: tuple = ("1cm", "1cm"),
328        z_index: int = 0,
329        presentation_class: str | None = None,
330        anchor_type: str | None = None,
331        anchor_page: int | None = None,
332        layer: str | None = None,
333        presentation_style: str | None = None,
334        **kwargs: Any,
335    ) -> Element:
336        """Create a ready-to-use text box, since text box must be embedded in
337        a frame.
338
339        The optionnal text will appear above the image.
340
341        Arguments:
342
343            text_or_element -- str or Element, or list of them, text content
344                               of the text box.
345
346            text_style -- str, name of the style for the text
347
348            See Frame() initialization for the other arguments
349
350        Return: Frame
351        """
352        frame = cls(
353            name=name,
354            draw_id=draw_id,
355            style=style,
356            position=position,
357            size=size,
358            z_index=z_index,
359            presentation_class=presentation_class,
360            anchor_type=anchor_type,
361            anchor_page=anchor_page,
362            layer=layer,
363            presentation_style=presentation_style,
364            **kwargs,
365        )
366        frame.set_text_box(text_or_element, text_style)
367        return frame
368
369    @property
370    def text_content(self) -> str:
371        text_box = self.get_element("draw:text-box")
372        if text_box is None:
373            return ""
374        return text_box.text_content
375
376    @text_content.setter
377    def text_content(self, text_or_element: Element | str) -> None:
378        text_box = self.get_element("draw:text-box")
379        if text_box is None:
380            text_box = Element.from_tag("draw:text-box")
381            self.append(text_box)
382        if isinstance(text_or_element, Element):
383            text_box.clear()
384            text_box.append(text_or_element)
385        else:
386            text_box.text_content = text_or_element
387
388    def get_image(
389        self,
390        position: int = 0,
391        name: str | None = None,
392        url: str | None = None,
393        content: str | None = None,
394    ) -> Element | None:
395        return self.get_element("draw:image")
396
397    def set_image(self, url_or_element: Element | str) -> Element:
398        image = self.get_image()
399        if image is None:
400            if isinstance(url_or_element, Element):
401                image = url_or_element
402                self.append(image)
403            else:
404                image = DrawImage(url_or_element)
405                self.append(image)
406        else:
407            if isinstance(url_or_element, Element):
408                image.delete()
409                image = url_or_element
410                self.append(image)
411            else:
412                image.set_url(url_or_element)  # type: ignore
413        return image
414
415    def get_text_box(self) -> Element | None:
416        return self.get_element("draw:text-box")
417
418    def set_text_box(
419        self,
420        text_or_element: Iterable[Element | str] | Element | str,
421        text_style: str | None = None,
422    ) -> Element:
423        text_box = self.get_text_box()
424        if text_box is None:
425            text_box = Element.from_tag("draw:text-box")
426            self.append(text_box)
427        else:
428            text_box.clear()
429        if isinstance(text_or_element, (Element, str)):
430            text_or_element_list: Iterable[Element | str] = [text_or_element]
431        else:
432            text_or_element_list = text_or_element
433        for item in text_or_element_list:
434            if isinstance(item, str):
435                text_box.append(Paragraph(item, style=text_style))
436            else:
437                text_box.append(item)
438        return text_box
439
440    @staticmethod
441    def _get_formatted_text_subresult(context: dict, element: Element) -> str:
442        str_list = ["  "]
443        for child in element.children:
444            str_list.append(child.get_formatted_text(context))
445        subresult = "".join(str_list)
446        subresult = subresult.replace("\n", "\n  ")
447        return subresult.rstrip(" ")
448
449    def get_formatted_text(  # noqa:  C901
450        self,
451        context: dict | None = None,
452    ) -> str:
453        if not context:
454            context = {}
455        result = []
456        for element in self.children:
457            tag = element.tag
458            if tag == "draw:image":
459                if context["rst_mode"]:
460                    filename = element.get_attribute("xlink:href")
461
462                    # Compute width and height
463                    width, height = self.size
464                    if width is not None:
465                        width = Unit(width)
466                        width = width.convert("px", DPI)
467                    if height is not None:
468                        height = Unit(height)
469                        height = height.convert("px", DPI)
470
471                    # Insert or not ?
472                    if context["no_img_level"]:
473                        context["img_counter"] += 1
474                        ref = f"|img{context['img_counter']}|"
475                        result.append(ref)
476                        context["images"].append((ref, filename, (width, height)))
477                    else:
478                        result.append(f"\n.. image:: {filename}\n")
479                        if width is not None:
480                            result.append(f"   :width: {width}\n")
481                        if height is not None:
482                            result.append(f"   :height: {height}\n")
483                else:
484                    result.append(f"[Image {element.get_attribute('xlink:href')}]\n")
485            elif tag == "draw:text-box":
486                result.append(self._get_formatted_text_subresult(context, element))
487            else:
488                result.append(element.get_formatted_text(context))
489        result.append("\n")
490        return "".join(result)

ODF Frame "draw:frame"

Frames are not useful by themselves. You should consider calling Frame.image_frame() or Frame.text_frame directly.

Frame( name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any)
189    def __init__(  # noqa:  C901
190        self,
191        name: str | None = None,
192        draw_id: str | None = None,
193        style: str | None = None,
194        position: tuple | None = None,
195        size: tuple = ("1cm", "1cm"),
196        z_index: int = 0,
197        presentation_class: str | None = None,
198        anchor_type: str | None = None,
199        anchor_page: int | None = None,
200        layer: str | None = None,
201        presentation_style: str | None = None,
202        **kwargs: Any,
203    ) -> None:
204        """Create a frame element of the given size. Position is relative to the
205        context the frame is inserted in. If positioned by page, give the page
206        number and the x, y position.
207
208        Size is a (width, height) tuple and position is a (left, top) tuple; items
209        are strings including the unit, e.g. ('10cm', '15cm').
210
211        Frames are not useful by themselves. You should consider calling:
212            Frame.image_frame()
213        or
214            Frame.text_frame()
215
216
217        Arguments:
218
219            name -- str
220
221            draw_id -- str
222
223            style -- str
224
225            position -- (str, str)
226
227            size -- (str, str)
228
229            z_index -- int (default 0)
230
231            presentation_class -- str
232
233            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
234
235            anchor_page -- int, page number is anchor_type is 'page'
236
237            layer -- str
238
239            presentation_style -- str
240        """
241        super().__init__(**kwargs)
242        if self._do_init:
243            self.size = size
244            self.z_index = z_index
245            if name:
246                self.name = name
247            if draw_id is not None:
248                self.draw_id = draw_id
249            if style is not None:
250                self.style = style
251            if position is not None:
252                self.position = position
253            if presentation_class is not None:
254                self.presentation_class = presentation_class
255            if anchor_type:
256                self.anchor_type = anchor_type
257            if position and not anchor_type:
258                self.anchor_type = "paragraph"
259            if anchor_page is not None:
260                self.anchor_page = anchor_page
261            if layer is not None:
262                self.layer = layer
263            if presentation_style is not None:
264                self.presentation_style = presentation_style

Create a frame element of the given size. Position is relative to the context the frame is inserted in. If positioned by page, give the page number and the x, y position.

Size is a (width, height) tuple and position is a (left, top) tuple; items are strings including the unit, e.g. ('10cm', '15cm').

Frames are not useful by themselves. You should consider calling: Frame.image_frame() or Frame.text_frame()

Arguments:

name -- str

draw_id -- str

style -- str

position -- (str, str)

size -- (str, str)

z_index -- int (default 0)

presentation_class -- str

anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'

anchor_page -- int, page number is anchor_type is 'page'

layer -- str

presentation_style -- str
@classmethod
def image_frame( cls, image: Element | str, text: str | None = None, name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any) -> Element:
266    @classmethod
267    def image_frame(
268        cls,
269        image: Element | str,
270        text: str | None = None,
271        name: str | None = None,
272        draw_id: str | None = None,
273        style: str | None = None,
274        position: tuple | None = None,
275        size: tuple = ("1cm", "1cm"),
276        z_index: int = 0,
277        presentation_class: str | None = None,
278        anchor_type: str | None = None,
279        anchor_page: int | None = None,
280        layer: str | None = None,
281        presentation_style: str | None = None,
282        **kwargs: Any,
283    ) -> Element:
284        """Create a ready-to-use image, since image must be embedded in a
285        frame.
286
287        The optionnal text will appear above the image.
288
289        Arguments:
290
291            image -- DrawImage or str, DrawImage element or URL of the image
292
293            text -- str, text for the image
294
295            See Frame() initialization for the other arguments
296
297        Return: Frame
298        """
299        frame = cls(
300            name=name,
301            draw_id=draw_id,
302            style=style,
303            position=position,
304            size=size,
305            z_index=z_index,
306            presentation_class=presentation_class,
307            anchor_type=anchor_type,
308            anchor_page=anchor_page,
309            layer=layer,
310            presentation_style=presentation_style,
311            **kwargs,
312        )
313        image_element = frame.set_image(image)
314        if text:
315            image_element.text_content = text
316        return frame

Create a ready-to-use image, since image must be embedded in a frame.

The optionnal text will appear above the image.

Arguments:

image -- DrawImage or str, DrawImage element or URL of the image

text -- str, text for the image

See Frame() initialization for the other arguments

Return: Frame

@classmethod
def text_frame( cls, text_or_element: collections.abc.Iterable[Element] | Element | str, text_style: str | None = None, name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any) -> Element:
318    @classmethod
319    def text_frame(
320        cls,
321        text_or_element: Iterable[Element] | Element | str,
322        text_style: str | None = None,
323        name: str | None = None,
324        draw_id: str | None = None,
325        style: str | None = None,
326        position: tuple | None = None,
327        size: tuple = ("1cm", "1cm"),
328        z_index: int = 0,
329        presentation_class: str | None = None,
330        anchor_type: str | None = None,
331        anchor_page: int | None = None,
332        layer: str | None = None,
333        presentation_style: str | None = None,
334        **kwargs: Any,
335    ) -> Element:
336        """Create a ready-to-use text box, since text box must be embedded in
337        a frame.
338
339        The optionnal text will appear above the image.
340
341        Arguments:
342
343            text_or_element -- str or Element, or list of them, text content
344                               of the text box.
345
346            text_style -- str, name of the style for the text
347
348            See Frame() initialization for the other arguments
349
350        Return: Frame
351        """
352        frame = cls(
353            name=name,
354            draw_id=draw_id,
355            style=style,
356            position=position,
357            size=size,
358            z_index=z_index,
359            presentation_class=presentation_class,
360            anchor_type=anchor_type,
361            anchor_page=anchor_page,
362            layer=layer,
363            presentation_style=presentation_style,
364            **kwargs,
365        )
366        frame.set_text_box(text_or_element, text_style)
367        return frame

Create a ready-to-use text box, since text box must be embedded in a frame.

The optionnal text will appear above the image.

Arguments:

text_or_element -- str or Element, or list of them, text content
                   of the text box.

text_style -- str, name of the style for the text

See Frame() initialization for the other arguments

Return: Frame

text_content: str
369    @property
370    def text_content(self) -> str:
371        text_box = self.get_element("draw:text-box")
372        if text_box is None:
373            return ""
374        return text_box.text_content

Get / set the text of the embedded paragraph, including embeded annotations, cells...

Set create a paragraph if missing

def get_image( self, position: int = 0, name: str | None = None, url: str | None = None, content: str | None = None) -> Element | None:
388    def get_image(
389        self,
390        position: int = 0,
391        name: str | None = None,
392        url: str | None = None,
393        content: str | None = None,
394    ) -> Element | None:
395        return self.get_element("draw:image")

Return the image matching the criteria.

Arguments:

position -- int

name -- str

url -- str regex

content -- str regex

Return: Element or None if not found

def set_image( self, url_or_element: Element | str) -> Element:
397    def set_image(self, url_or_element: Element | str) -> Element:
398        image = self.get_image()
399        if image is None:
400            if isinstance(url_or_element, Element):
401                image = url_or_element
402                self.append(image)
403            else:
404                image = DrawImage(url_or_element)
405                self.append(image)
406        else:
407            if isinstance(url_or_element, Element):
408                image.delete()
409                image = url_or_element
410                self.append(image)
411            else:
412                image.set_url(url_or_element)  # type: ignore
413        return image
def get_text_box(self) -> Element | None:
415    def get_text_box(self) -> Element | None:
416        return self.get_element("draw:text-box")
def set_text_box( self, text_or_element: collections.abc.Iterable[Element | str] | Element | str, text_style: str | None = None) -> Element:
418    def set_text_box(
419        self,
420        text_or_element: Iterable[Element | str] | Element | str,
421        text_style: str | None = None,
422    ) -> Element:
423        text_box = self.get_text_box()
424        if text_box is None:
425            text_box = Element.from_tag("draw:text-box")
426            self.append(text_box)
427        else:
428            text_box.clear()
429        if isinstance(text_or_element, (Element, str)):
430            text_or_element_list: Iterable[Element | str] = [text_or_element]
431        else:
432            text_or_element_list = text_or_element
433        for item in text_or_element_list:
434            if isinstance(item, str):
435                text_box.append(Paragraph(item, style=text_style))
436            else:
437                text_box.append(item)
438        return text_box
def get_formatted_text(self, context: dict | None = None) -> str:
449    def get_formatted_text(  # noqa:  C901
450        self,
451        context: dict | None = None,
452    ) -> str:
453        if not context:
454            context = {}
455        result = []
456        for element in self.children:
457            tag = element.tag
458            if tag == "draw:image":
459                if context["rst_mode"]:
460                    filename = element.get_attribute("xlink:href")
461
462                    # Compute width and height
463                    width, height = self.size
464                    if width is not None:
465                        width = Unit(width)
466                        width = width.convert("px", DPI)
467                    if height is not None:
468                        height = Unit(height)
469                        height = height.convert("px", DPI)
470
471                    # Insert or not ?
472                    if context["no_img_level"]:
473                        context["img_counter"] += 1
474                        ref = f"|img{context['img_counter']}|"
475                        result.append(ref)
476                        context["images"].append((ref, filename, (width, height)))
477                    else:
478                        result.append(f"\n.. image:: {filename}\n")
479                        if width is not None:
480                            result.append(f"   :width: {width}\n")
481                        if height is not None:
482                            result.append(f"   :height: {height}\n")
483                else:
484                    result.append(f"[Image {element.get_attribute('xlink:href')}]\n")
485            elif tag == "draw:text-box":
486                result.append(self._get_formatted_text_subresult(context, element))
487            else:
488                result.append(element.get_formatted_text(context))
489        result.append("\n")
490        return "".join(result)

This function should return a beautiful version of the text.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
draw_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
width: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
height: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_x: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_y: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_class: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
layer: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.AnchorMix
ANCHOR_VALUE_CHOICE
anchor_type
anchor_page
odfdo.frame.PosMix
position
odfdo.frame.ZMix
z_index
odfdo.frame.SizeMix
size
class HeaderRows(odfdo.Element):
32class HeaderRows(Element):
33    _tag = "table:table-header-rows"
34    _caching = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class IndexTitle(odfdo.Element):
40class IndexTitle(Element):
41    """The "text:index-title" element contains the title of an index.
42
43    The element has the following attributes:
44    text:name, text:protected, text:protection-key,
45    text:protection-key-digest-algorithm, text:style-name, xml:id.
46
47    The actual title is stored in a child element
48    """
49
50    _tag = "text:index-title"
51    _properties = (
52        PropDef("name", "text:name"),
53        PropDef("style", "text:style-name"),
54        PropDef("xml_id", "xml:id"),
55        PropDef("protected", "text:protected"),
56        PropDef("protection_key", "text:protection-key"),
57        PropDef(
58            "protection_key_digest_algorithm", "text:protection-key-digest-algorithm"
59        ),
60    )
61
62    def __init__(
63        self,
64        name: str | None = None,
65        style: str | None = None,
66        title_text: str | None = None,
67        title_text_style: str | None = None,
68        xml_id: str | None = None,
69        **kwargs: Any,
70    ) -> None:
71        super().__init__(**kwargs)
72        if self._do_init:
73            if name:
74                self.name = name
75            if style:
76                self.style = style
77            if xml_id:
78                self.xml_id = xml_id
79            if title_text:
80                self.set_title_text(title_text, title_text_style)
81
82    def set_title_text(
83        self,
84        title_text: str,
85        title_text_style: str | None = None,
86    ) -> None:
87        title = Paragraph(title_text, style=title_text_style)
88        self.append(title)

The "text:index-title" element contains the title of an index.

The element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name, xml:id.

The actual title is stored in a child element

IndexTitle( name: str | None = None, style: str | None = None, title_text: str | None = None, title_text_style: str | None = None, xml_id: str | None = None, **kwargs: Any)
62    def __init__(
63        self,
64        name: str | None = None,
65        style: str | None = None,
66        title_text: str | None = None,
67        title_text_style: str | None = None,
68        xml_id: str | None = None,
69        **kwargs: Any,
70    ) -> None:
71        super().__init__(**kwargs)
72        if self._do_init:
73            if name:
74                self.name = name
75            if style:
76                self.style = style
77            if xml_id:
78                self.xml_id = xml_id
79            if title_text:
80                self.set_title_text(title_text, title_text_style)
def set_title_text(self, title_text: str, title_text_style: str | None = None) -> None:
82    def set_title_text(
83        self,
84        title_text: str,
85        title_text_style: str | None = None,
86    ) -> None:
87        title = Paragraph(title_text, style=title_text_style)
88        self.append(title)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
xml_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protected: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key_digest_algorithm: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class IndexTitleTemplate(odfdo.Element):
447class IndexTitleTemplate(Element):
448    """ODF "text:index-title-template"
449
450    Arguments:
451
452        style -- str
453    """
454
455    _tag = "text:index-title-template"
456    _properties = (PropDef("style", "text:style-name"),)
457
458    def __init__(self, style: str | None = None, **kwargs: Any) -> None:
459        super().__init__(**kwargs)
460        if self._do_init and style:
461            self.style = style

ODF "text:index-title-template"

Arguments:

style -- str
IndexTitleTemplate(style: str | None = None, **kwargs: Any)
458    def __init__(self, style: str | None = None, **kwargs: Any) -> None:
459        super().__init__(**kwargs)
460        if self._do_init and style:
461            self.style = style
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
LAST_CHILD = 1
class LineBreak(odfdo.Element):
223class LineBreak(Element):
224    """This element represents a line break "text:line-break" """
225
226    _tag = "text:line-break"
227
228    def __init__(self, **kwargs: Any) -> None:
229        super().__init__(**kwargs)

This element represents a line break "text:line-break"

LineBreak(**kwargs: Any)
228    def __init__(self, **kwargs: Any) -> None:
229        super().__init__(**kwargs)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class LineShape(odfdo.shapes.ShapeBase):
 89class LineShape(ShapeBase):
 90    """Create a line shape.
 91
 92    Arguments:
 93
 94        style -- str
 95
 96        text_style -- str
 97
 98        draw_id -- str
 99
100        layer -- str
101
102        p1 -- (str, str)
103
104        p2 -- (str, str)
105    """
106
107    _tag = "draw:line"
108    _properties: tuple[PropDef, ...] = (
109        PropDef("x1", "svg:x1"),
110        PropDef("y1", "svg:y1"),
111        PropDef("x2", "svg:x2"),
112        PropDef("y2", "svg:y2"),
113    )
114
115    def __init__(
116        self,
117        style: str | None = None,
118        text_style: str | None = None,
119        draw_id: str | None = None,
120        layer: str | None = None,
121        p1: tuple | None = None,
122        p2: tuple | None = None,
123        **kwargs: Any,
124    ) -> None:
125        kwargs.update(
126            {
127                "style": style,
128                "text_style": text_style,
129                "draw_id": draw_id,
130                "layer": layer,
131            }
132        )
133        super().__init__(**kwargs)
134        if self._do_init:
135            if p1:
136                self.x1 = p1[0]
137                self.y1 = p1[1]
138            if p2:
139                self.x2 = p2[0]
140                self.y2 = p2[1]

Create a line shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

p1 -- (str, str)

p2 -- (str, str)
LineShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, p1: tuple | None = None, p2: tuple | None = None, **kwargs: Any)
115    def __init__(
116        self,
117        style: str | None = None,
118        text_style: str | None = None,
119        draw_id: str | None = None,
120        layer: str | None = None,
121        p1: tuple | None = None,
122        p2: tuple | None = None,
123        **kwargs: Any,
124    ) -> None:
125        kwargs.update(
126            {
127                "style": style,
128                "text_style": text_style,
129                "draw_id": draw_id,
130                "layer": layer,
131            }
132        )
133        super().__init__(**kwargs)
134        if self._do_init:
135            if p1:
136                self.x1 = p1[0]
137                self.y1 = p1[1]
138            if p2:
139                self.x2 = p2[0]
140                self.y2 = p2[1]
x1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
x2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class List(odfdo.Element):
 68class List(Element):
 69    """ODF List "text:list"."""
 70
 71    _tag = "text:list"
 72    _properties = (PropDef("style", "text:style-name"),)
 73
 74    def __init__(
 75        self,
 76        list_content: str | Element | Iterable[str | Element] | None = None,
 77        style: str | None = None,
 78        **kwargs: Any,
 79    ) -> None:
 80        """Create a list element, optionaly loading the list by a list of
 81        item (str or elements).
 82
 83        The list_content argument is just a shortcut for the most common case.
 84        To create more complex lists, first create an empty list, and fill it
 85        afterwards.
 86
 87        Arguments:
 88
 89            list_content -- str or Element, or a list of str or Element
 90
 91            style -- str
 92        """
 93        super().__init__(**kwargs)
 94        if self._do_init:
 95            if list_content:
 96                if isinstance(list_content, (Element, str)):
 97                    self.append(ListItem(list_content))
 98                elif hasattr(list_content, "__iter__"):
 99                    for item in list_content:
100                        self.append(ListItem(item))
101            if style is not None:
102                self.style = style
103
104    def get_items(self, content: str | None = None) -> list[Element]:
105        """Return all the list items that match the criteria.
106
107        Arguments:
108
109            style -- str
110
111            content -- str regex
112
113        Return: list of Element
114        """
115        return self._filtered_elements("text:list-item", content=content)
116
117    def get_item(
118        self,
119        position: int = 0,
120        content: str | None = None,
121    ) -> Element | None:
122        """Return the list item that matches the criteria. In nested lists,
123        return the list item that really contains that content.
124
125        Arguments:
126
127            position -- int
128
129            content -- str regex
130
131        Return: Element or None if not found
132        """
133        # Custom implementation because of nested lists
134        if content:
135            # Don't search recursively but on the very own paragraph(s) of
136            # each list item
137            for paragraph in self.get_elements("descendant::text:p"):
138                if paragraph.match(content):
139                    return paragraph.get_element("parent::text:list-item")
140            return None
141        return self._filtered_element("text:list-item", position)
142
143    def set_list_header(
144        self,
145        text_or_element: str | Element | Iterable[str | Element],
146    ) -> None:
147        if isinstance(text_or_element, (str, Element)):
148            actual_list: list[str | Element] | tuple = [text_or_element]
149        elif isinstance(text_or_element, (list, tuple)):
150            actual_list = text_or_element
151        else:
152            raise TypeError
153        # Remove existing header
154        for element in self.get_elements("text:p"):
155            self.delete(element)
156        for paragraph in reversed(actual_list):
157            if isinstance(paragraph, str):
158                paragraph = Paragraph(paragraph)
159            self.insert(paragraph, FIRST_CHILD)
160
161    def insert_item(
162        self,
163        item: ListItem | str | Element | None,
164        position: int | None = None,
165        before: Element | None = None,
166        after: Element | None = None,
167    ) -> None:
168        if not isinstance(item, ListItem):
169            item = ListItem(item)
170        if before is not None:
171            before.insert(item, xmlposition=PREV_SIBLING)
172        elif after is not None:
173            after.insert(item, xmlposition=NEXT_SIBLING)
174        elif position is not None:
175            self.insert(item, position=position)
176        else:
177            raise ValueError("Position must be defined")
178
179    def append_item(
180        self,
181        item: ListItem | str | Element | None,
182    ) -> None:
183        if not isinstance(item, ListItem):
184            item = ListItem(item)
185        self.append(item)
186
187    def get_formatted_text(self, context: dict | None = None) -> str:
188        if context is None:
189            context = {}
190        rst_mode = context["rst_mode"]
191        result = []
192        if rst_mode:
193            result.append("\n")
194        for list_item in self.get_elements("text:list-item"):
195            textbuf = []
196            for child in list_item.children:
197                text = child.get_formatted_text(context)
198                tag = child.tag
199                if tag == "text:h":
200                    # A title in a list is a bug
201                    return text
202                if tag == "text:list" and not text.lstrip().startswith("-"):
203                    # If the list didn't indent, don't either
204                    # (inner title)
205                    return text
206                textbuf.append(text)
207            text_sum = "".join(textbuf)
208            text_sum = text_sum.strip("\n")
209            # Indent the text
210            text_sum = text_sum.replace("\n", "\n  ")
211            text_sum = f"- {text_sum}\n"
212            result.append(text_sum)
213        if rst_mode:
214            result.append("\n")
215        return "".join(result)

ODF List "text:list".

List( list_content: str | Element | collections.abc.Iterable[str | Element] | None = None, style: str | None = None, **kwargs: Any)
 74    def __init__(
 75        self,
 76        list_content: str | Element | Iterable[str | Element] | None = None,
 77        style: str | None = None,
 78        **kwargs: Any,
 79    ) -> None:
 80        """Create a list element, optionaly loading the list by a list of
 81        item (str or elements).
 82
 83        The list_content argument is just a shortcut for the most common case.
 84        To create more complex lists, first create an empty list, and fill it
 85        afterwards.
 86
 87        Arguments:
 88
 89            list_content -- str or Element, or a list of str or Element
 90
 91            style -- str
 92        """
 93        super().__init__(**kwargs)
 94        if self._do_init:
 95            if list_content:
 96                if isinstance(list_content, (Element, str)):
 97                    self.append(ListItem(list_content))
 98                elif hasattr(list_content, "__iter__"):
 99                    for item in list_content:
100                        self.append(ListItem(item))
101            if style is not None:
102                self.style = style

Create a list element, optionaly loading the list by a list of item (str or elements).

The list_content argument is just a shortcut for the most common case. To create more complex lists, first create an empty list, and fill it afterwards.

Arguments:

list_content -- str or Element, or a list of str or Element

style -- str
def get_items(self, content: str | None = None) -> list[Element]:
104    def get_items(self, content: str | None = None) -> list[Element]:
105        """Return all the list items that match the criteria.
106
107        Arguments:
108
109            style -- str
110
111            content -- str regex
112
113        Return: list of Element
114        """
115        return self._filtered_elements("text:list-item", content=content)

Return all the list items that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Element

def get_item( self, position: int = 0, content: str | None = None) -> Element | None:
117    def get_item(
118        self,
119        position: int = 0,
120        content: str | None = None,
121    ) -> Element | None:
122        """Return the list item that matches the criteria. In nested lists,
123        return the list item that really contains that content.
124
125        Arguments:
126
127            position -- int
128
129            content -- str regex
130
131        Return: Element or None if not found
132        """
133        # Custom implementation because of nested lists
134        if content:
135            # Don't search recursively but on the very own paragraph(s) of
136            # each list item
137            for paragraph in self.get_elements("descendant::text:p"):
138                if paragraph.match(content):
139                    return paragraph.get_element("parent::text:list-item")
140            return None
141        return self._filtered_element("text:list-item", position)

Return the list item that matches the criteria. In nested lists, return the list item that really contains that content.

Arguments:

position -- int

content -- str regex

Return: Element or None if not found

def set_list_header( self, text_or_element: str | Element | collections.abc.Iterable[str | Element]) -> None:
143    def set_list_header(
144        self,
145        text_or_element: str | Element | Iterable[str | Element],
146    ) -> None:
147        if isinstance(text_or_element, (str, Element)):
148            actual_list: list[str | Element] | tuple = [text_or_element]
149        elif isinstance(text_or_element, (list, tuple)):
150            actual_list = text_or_element
151        else:
152            raise TypeError
153        # Remove existing header
154        for element in self.get_elements("text:p"):
155            self.delete(element)
156        for paragraph in reversed(actual_list):
157            if isinstance(paragraph, str):
158                paragraph = Paragraph(paragraph)
159            self.insert(paragraph, FIRST_CHILD)
def insert_item( self, item: ListItem | str | Element | None, position: int | None = None, before: Element | None = None, after: Element | None = None) -> None:
161    def insert_item(
162        self,
163        item: ListItem | str | Element | None,
164        position: int | None = None,
165        before: Element | None = None,
166        after: Element | None = None,
167    ) -> None:
168        if not isinstance(item, ListItem):
169            item = ListItem(item)
170        if before is not None:
171            before.insert(item, xmlposition=PREV_SIBLING)
172        elif after is not None:
173            after.insert(item, xmlposition=NEXT_SIBLING)
174        elif position is not None:
175            self.insert(item, position=position)
176        else:
177            raise ValueError("Position must be defined")
def append_item( self, item: ListItem | str | Element | None) -> None:
179    def append_item(
180        self,
181        item: ListItem | str | Element | None,
182    ) -> None:
183        if not isinstance(item, ListItem):
184            item = ListItem(item)
185        self.append(item)
def get_formatted_text(self, context: dict | None = None) -> str:
187    def get_formatted_text(self, context: dict | None = None) -> str:
188        if context is None:
189            context = {}
190        rst_mode = context["rst_mode"]
191        result = []
192        if rst_mode:
193            result.append("\n")
194        for list_item in self.get_elements("text:list-item"):
195            textbuf = []
196            for child in list_item.children:
197                text = child.get_formatted_text(context)
198                tag = child.tag
199                if tag == "text:h":
200                    # A title in a list is a bug
201                    return text
202                if tag == "text:list" and not text.lstrip().startswith("-"):
203                    # If the list didn't indent, don't either
204                    # (inner title)
205                    return text
206                textbuf.append(text)
207            text_sum = "".join(textbuf)
208            text_sum = text_sum.strip("\n")
209            # Indent the text
210            text_sum = text_sum.replace("\n", "\n  ")
211            text_sum = f"- {text_sum}\n"
212            result.append(text_sum)
213        if rst_mode:
214            result.append("\n")
215        return "".join(result)

This function should return a beautiful version of the text.

style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ListItem(odfdo.Element):
41class ListItem(Element):
42    """ODF element "text:list-item", item of a List."""
43
44    _tag = "text:list-item"
45
46    def __init__(
47        self,
48        text_or_element: str | Element | None = None,
49        **kwargs: Any,
50    ) -> None:
51        """Create a list item element, optionaly passing at creation time a
52        string or Element as content.
53
54        Arguments:
55
56            text_or_element -- str or ODF Element
57        """
58        super().__init__(**kwargs)
59        if self._do_init:
60            if isinstance(text_or_element, str):
61                self.text_content = text_or_element
62            elif isinstance(text_or_element, Element):
63                self.append(text_or_element)
64            elif text_or_element is not None:
65                raise TypeError("Expected str or Element")

ODF element "text:list-item", item of a List.

ListItem( text_or_element: str | Element | None = None, **kwargs: Any)
46    def __init__(
47        self,
48        text_or_element: str | Element | None = None,
49        **kwargs: Any,
50    ) -> None:
51        """Create a list item element, optionaly passing at creation time a
52        string or Element as content.
53
54        Arguments:
55
56            text_or_element -- str or ODF Element
57        """
58        super().__init__(**kwargs)
59        if self._do_init:
60            if isinstance(text_or_element, str):
61                self.text_content = text_or_element
62            elif isinstance(text_or_element, Element):
63                self.append(text_or_element)
64            elif text_or_element is not None:
65                raise TypeError("Expected str or Element")

Create a list item element, optionaly passing at creation time a string or Element as content.

Arguments:

text_or_element -- str or ODF Element
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Manifest(odfdo.XmlPart):
 31class Manifest(XmlPart):
 32    def get_paths(self) -> list[Element | Text]:
 33        """Return the list of full paths in the manifest.
 34
 35        Return: list of str
 36        """
 37        xpath_query = "//manifest:file-entry/attribute::manifest:full-path"
 38        return self.xpath(xpath_query)
 39
 40    def _file_entry(self, full_path: str) -> Element:
 41        xpath_query = (
 42            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
 43        )
 44        result = self.xpath(xpath_query)
 45        if not result:
 46            raise KeyError(f"Path not found: '{full_path}'")
 47        return result[0]  # type: ignore
 48
 49    def get_path_medias(self) -> list[tuple]:
 50        """Return the list of (full_path, media_type) pairs in the manifest.
 51
 52        Return: list of str tuples
 53        """
 54        xpath_query = "//manifest:file-entry"
 55        result = []
 56        for file_entry in self.xpath(xpath_query):
 57            if not isinstance(file_entry, Element):
 58                continue
 59            result.append(
 60                (
 61                    file_entry.get_attribute_string("manifest:full-path"),
 62                    file_entry.get_attribute_string("manifest:media-type"),
 63                )
 64            )
 65        return result
 66
 67    def get_media_type(self, full_path: str) -> str | None:
 68        """Get the media type of an existing path.
 69
 70        Return: str
 71        """
 72        xpath_query = (
 73            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
 74            "/attribute::manifest:media-type"
 75        )
 76        result = self.xpath(xpath_query)
 77        if not result:
 78            return None
 79        return str(result[0])
 80
 81    def set_media_type(self, full_path: str, media_type: str) -> None:
 82        """Set the media type of an existing path.
 83
 84        Arguments:
 85
 86            full_path -- str
 87
 88            media_type -- str
 89        """
 90        file_entry = self._file_entry(full_path)
 91        file_entry.set_attribute("manifest:media-type", media_type)
 92
 93    @staticmethod
 94    def make_file_entry(full_path: str, media_type: str) -> Element:
 95        tag = (
 96            f"<manifest:file-entry "
 97            f'manifest:media-type="{media_type}" '
 98            f'manifest:full-path="{full_path}"/>'
 99        )
100        return Element.from_tag(tag)
101
102    def add_full_path(self, full_path: str, media_type: str = "") -> None:
103        # Existing?
104        existing = self.get_media_type(full_path)
105        if existing is not None:
106            self.set_media_type(full_path, media_type)
107        root = self.root
108        root.append(self.make_file_entry(full_path, media_type))
109
110    def del_full_path(self, full_path: str) -> None:
111        file_entry = self._file_entry(full_path)
112        self.root.delete(file_entry)

Representation of an XML part.

Abstraction of the XML library behind.

def get_paths(self) -> list[Element | Text]:
32    def get_paths(self) -> list[Element | Text]:
33        """Return the list of full paths in the manifest.
34
35        Return: list of str
36        """
37        xpath_query = "//manifest:file-entry/attribute::manifest:full-path"
38        return self.xpath(xpath_query)

Return the list of full paths in the manifest.

Return: list of str

def get_path_medias(self) -> list[tuple]:
49    def get_path_medias(self) -> list[tuple]:
50        """Return the list of (full_path, media_type) pairs in the manifest.
51
52        Return: list of str tuples
53        """
54        xpath_query = "//manifest:file-entry"
55        result = []
56        for file_entry in self.xpath(xpath_query):
57            if not isinstance(file_entry, Element):
58                continue
59            result.append(
60                (
61                    file_entry.get_attribute_string("manifest:full-path"),
62                    file_entry.get_attribute_string("manifest:media-type"),
63                )
64            )
65        return result

Return the list of (full_path, media_type) pairs in the manifest.

Return: list of str tuples

def get_media_type(self, full_path: str) -> str | None:
67    def get_media_type(self, full_path: str) -> str | None:
68        """Get the media type of an existing path.
69
70        Return: str
71        """
72        xpath_query = (
73            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
74            "/attribute::manifest:media-type"
75        )
76        result = self.xpath(xpath_query)
77        if not result:
78            return None
79        return str(result[0])

Get the media type of an existing path.

Return: str

def set_media_type(self, full_path: str, media_type: str) -> None:
81    def set_media_type(self, full_path: str, media_type: str) -> None:
82        """Set the media type of an existing path.
83
84        Arguments:
85
86            full_path -- str
87
88            media_type -- str
89        """
90        file_entry = self._file_entry(full_path)
91        file_entry.set_attribute("manifest:media-type", media_type)

Set the media type of an existing path.

Arguments:

full_path -- str

media_type -- str
@staticmethod
def make_file_entry(full_path: str, media_type: str) -> Element:
 93    @staticmethod
 94    def make_file_entry(full_path: str, media_type: str) -> Element:
 95        tag = (
 96            f"<manifest:file-entry "
 97            f'manifest:media-type="{media_type}" '
 98            f'manifest:full-path="{full_path}"/>'
 99        )
100        return Element.from_tag(tag)
def add_full_path(self, full_path: str, media_type: str = '') -> None:
102    def add_full_path(self, full_path: str, media_type: str = "") -> None:
103        # Existing?
104        existing = self.get_media_type(full_path)
105        if existing is not None:
106            self.set_media_type(full_path, media_type)
107        root = self.root
108        root.append(self.make_file_entry(full_path, media_type))
def del_full_path(self, full_path: str) -> None:
110    def del_full_path(self, full_path: str) -> None:
111        file_entry = self._file_entry(full_path)
112        self.root.delete(file_entry)
class Meta(odfdo.XmlPart):
 43class Meta(XmlPart):
 44    def __init__(self, *args: Any, **kwargs: Any) -> None:
 45        super().__init__(*args, **kwargs)
 46        self._generator_modified: bool = False
 47
 48    def get_meta_body(self) -> Element:
 49        return self.get_element("//office:meta")
 50
 51    def get_title(self) -> str | None:
 52        """Get the title of the document.
 53
 54        This is not the first heading but the title metadata.
 55
 56        Return: str (or None if inexistant)
 57        """
 58        element = self.get_element("//dc:title")
 59        if element is None:
 60            return None
 61        return element.text
 62
 63    def set_title(self, title: str) -> None:
 64        """Set the title of the document.
 65
 66        This is not the first heading but the title metadata.
 67
 68        Arguments:
 69
 70            title -- str
 71        """
 72        element = self.get_element("//dc:title")
 73        if element is None:
 74            element = Element.from_tag("dc:title")
 75            self.get_meta_body().append(element)
 76        element.text = title
 77
 78    def get_description(self) -> str | None:
 79        """Get the description of the document. Also known as comments.
 80
 81        Return: str (or None if inexistant)
 82        """
 83        element = self.get_element("//dc:description")
 84        if element is None:
 85            return None
 86        return element.text
 87
 88    # As named in OOo
 89    get_comments = get_description
 90
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description
103
104    set_comments = set_description
105
106    def get_subject(self) -> str | None:
107        """Get the subject of the document.
108
109        Return: str (or None if inexistant)
110        """
111        element = self.get_element("//dc:subject")
112        if element is None:
113            return None
114        return element.text
115
116    def set_subject(self, subject: str) -> None:
117        """Set the subject of the document.
118
119        Arguments:
120
121            subject -- str
122        """
123        element = self.get_element("//dc:subject")
124        if element is None:
125            element = Element.from_tag("dc:subject")
126            self.get_meta_body().append(element)
127        element.text = subject
128
129    def get_language(self) -> str | None:
130        """Get the language code of the document.
131
132        Return: str (or None if inexistant)
133
134        Example::
135
136            >>> document.meta.get_language()
137            fr-FR
138        """
139        element = self.get_element("//dc:language")
140        if element is None:
141            return None
142        return element.text
143
144    def set_language(self, language: str) -> None:
145        """Set the language code of the document.
146
147        Arguments:
148
149            language -- str
150
151        Example::
152
153            >>> document.meta.set_language('fr-FR')
154        """
155        language = str(language)
156        if not self._is_RFC3066(language):
157            raise TypeError(
158                'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)'
159            )
160        element = self.get_element("//dc:language")
161        if element is None:
162            element = Element.from_tag("dc:language")
163            self.get_meta_body().append(element)
164        element.text = language
165
166    @staticmethod
167    def _is_RFC3066(lang: str) -> bool:
168        def test_part1(part1: str) -> bool:
169            if not 2 <= len(part1) <= 3:
170                return False
171            return all(x in ascii_letters for x in part1)
172
173        def test_part2(part2: str) -> bool:
174            return all(x in ascii_letters or x in digits for x in part2)
175
176        if not lang or not isinstance(lang, str):
177            return False
178        if "-" not in lang:
179            return test_part1(lang)
180        parts = lang.split("-")
181        if len(parts) > 3:
182            return False
183        if not test_part1(parts[0]):
184            return False
185        return all(test_part2(p) for p in parts[1:])
186
187    def get_modification_date(self) -> datetime | None:
188        """Get the last modified date of the document.
189
190        Return: datetime (or None if inexistant)
191        """
192        element = self.get_element("//dc:date")
193        if element is None:
194            return None
195        modification_date = element.text
196        return DateTime.decode(modification_date)
197
198    def set_modification_date(self, date: datetime) -> None:
199        """Set the last modified date of the document.
200
201        Arguments:
202
203            date -- datetime
204        """
205        element = self.get_element("//dc:date")
206        if element is None:
207            element = Element.from_tag("dc:date")
208            self.get_meta_body().append(element)
209        element.text = DateTime.encode(date)
210
211    def get_creation_date(self) -> datetime | None:
212        """Get the creation date of the document.
213
214        Return: datetime (or None if inexistant)
215        """
216        element = self.get_element("//meta:creation-date")
217        if element is None:
218            return None
219        creation_date = element.text
220        return DateTime.decode(creation_date)
221
222    def set_creation_date(self, date: datetime) -> None:
223        """Set the creation date of the document.
224
225        Arguments:
226
227            date -- datetime
228        """
229        element = self.get_element("//meta:creation-date")
230        if element is None:
231            element = Element.from_tag("meta:creation-date")
232            self.get_meta_body().append(element)
233        element.text = DateTime.encode(date)
234
235    def get_initial_creator(self) -> str | None:
236        """Get the first creator of the document.
237
238        Return: str (or None if inexistant)
239
240        Example::
241
242            >>> document.meta.get_initial_creator()
243            Unknown
244        """
245        element = self.get_element("//meta:initial-creator")
246        if element is None:
247            return None
248        return element.text
249
250    def set_initial_creator(self, creator: str) -> None:
251        """Set the first creator of the document.
252
253        Arguments:
254
255            creator -- str
256
257        Example::
258
259            >>> document.meta.set_initial_creator("Plato")
260        """
261        element = self.get_element("//meta:initial-creator")
262        if element is None:
263            element = Element.from_tag("meta:initial-creator")
264            self.get_meta_body().append(element)
265        element.text = creator
266
267    def get_creator(self) -> str | None:
268        """Get the creator of the document.
269
270        Return: str (or None if inexistant)
271
272        Example::
273
274            >>> document.meta.get_creator()
275            Unknown
276        """
277        element = self.get_element("//dc:creator")
278        if element is None:
279            return None
280        return element.text
281
282    def set_creator(self, creator: str) -> None:
283        """Set the creator of the document.
284
285        Arguments:
286
287            creator -- str
288
289        Example::
290
291            >>> document.meta.set_creator("Plato")
292        """
293        element = self.get_element("//dc:creator")
294        if element is None:
295            element = Element.from_tag("dc:creator")
296            self.get_meta_body().append(element)
297        element.text = creator
298
299    def get_keywords(self) -> str | None:
300        """Get the keywords of the document. Return the field as-is, without
301        any assumption on the keyword separator.
302
303        Return: str (or None if inexistant)
304        """
305        element = self.get_element("//meta:keyword")
306        if element is None:
307            return None
308        return element.text
309
310    def set_keywords(self, keywords: str) -> None:
311        """Set the keywords of the document. Although the name is plural, a
312        str string is required, so join your list first.
313
314        Arguments:
315
316            keywords -- str
317        """
318        element = self.get_element("//meta:keyword")
319        if element is None:
320            element = Element.from_tag("meta:keyword")
321            self.get_meta_body().append(element)
322        element.text = keywords
323
324    def get_editing_duration(self) -> timedelta | None:
325        """Get the time the document was edited, as reported by the
326        generator.
327
328        Return: timedelta (or None if inexistant)
329        """
330        element = self.get_element("//meta:editing-duration")
331        if element is None:
332            return None
333        duration = element.text
334        return Duration.decode(duration)
335
336    def set_editing_duration(self, duration: timedelta) -> None:
337        """Set the time the document was edited.
338
339        Arguments:
340
341            duration -- timedelta
342        """
343        if not isinstance(duration, timedelta):
344            raise TypeError("duration must be a timedelta")
345        element = self.get_element("//meta:editing-duration")
346        if element is None:
347            element = Element.from_tag("meta:editing-duration")
348            self.get_meta_body().append(element)
349        element.text = Duration.encode(duration)
350
351    def get_editing_cycles(self) -> int | None:
352        """Get the number of times the document was edited, as reported by
353        the generator.
354
355        Return: int (or None if inexistant)
356        """
357        element = self.get_element("//meta:editing-cycles")
358        if element is None:
359            return None
360        cycles = element.text
361        return int(cycles)
362
363    def set_editing_cycles(self, cycles: int) -> None:
364        """Set the number of times the document was edited.
365
366        Arguments:
367
368            cycles -- int
369        """
370        if not isinstance(cycles, int):
371            raise TypeError("cycles must be an int")
372        if cycles < 1:
373            raise ValueError("cycles must be a positive int")
374        element = self.get_element("//meta:editing-cycles")
375        if element is None:
376            element = Element.from_tag("meta:editing-cycles")
377            self.get_meta_body().append(element)
378        element.text = str(cycles)
379
380    def get_generator(self) -> str | None:
381        """Get the signature of the software that generated this document.
382
383        Return: str (or None if inexistant)
384
385        Example::
386
387            >>> document.meta.get_generator()
388            KOffice/2.0.0
389        """
390        element = self.get_element("//meta:generator")
391        if element is None:
392            return None
393        return element.text
394
395    def set_generator(self, generator: str) -> None:
396        """Set the signature of the software that generated this document.
397
398        Arguments:
399
400            generator -- str
401
402        Example::
403
404            >>> document.meta.set_generator("Odfdo experiment")
405        """
406        element = self.get_element("//meta:generator")
407        if element is None:
408            element = Element.from_tag("meta:generator")
409            self.get_meta_body().append(element)
410        element.text = generator
411        self._generator_modified = True
412
413    def set_generator_default(self) -> None:
414        """Set the signature of the software that generated this document
415        to ourself.
416
417        Example::
418
419            >>> document.meta.set_generator_default()
420        """
421        if not self._generator_modified:
422            self.set_generator(GENERATOR)
423
424    def get_statistic(self) -> dict[str, int] | None:
425        """Get the statistic from the software that generated this document.
426
427        Return: dict (or None if inexistant)
428
429        Example::
430
431            >>> document.get_statistic():
432            {'meta:table-count': 1,
433             'meta:image-count': 2,
434             'meta:object-count': 3,
435             'meta:page-count': 4,
436             'meta:paragraph-count': 5,
437             'meta:word-count': 6,
438             'meta:character-count': 7}
439        """
440        element = self.get_element("//meta:document-statistic")
441        if element is None:
442            return None
443        statistic = {}
444        for key, value in element.attributes.items():
445            statistic[to_str(key)] = int(value)
446        return statistic
447
448    def set_statistic(self, statistic: dict[str, int]) -> None:
449        """Set the statistic for the documents: number of words, paragraphs,
450        etc.
451
452        Arguments:
453
454            statistic -- dict
455
456        Example::
457
458            >>> statistic = {'meta:table-count': 1,
459                             'meta:image-count': 2,
460                             'meta:object-count': 3,
461                             'meta:page-count': 4,
462                             'meta:paragraph-count': 5,
463                             'meta:word-count': 6,
464                             'meta:character-count': 7}
465            >>> document.meta.set_statistic(statistic)
466        """
467        if not isinstance(statistic, dict):
468            raise TypeError("Statistic must be a dict")
469        element = self.get_element("//meta:document-statistic")
470        for key, value in statistic.items():
471            try:
472                ivalue = int(value)
473            except ValueError as e:
474                raise TypeError("Statistic value must be a int") from e
475            element.set_attribute(to_str(key), str(ivalue))
476
477    def get_user_defined_metadata(self) -> dict[str, Any]:
478        """Return a dict of str/value mapping.
479
480        Value types can be: Decimal, date, time, boolean or str.
481        """
482        result: dict[str, Any] = {}
483        for item in self.get_elements("//meta:user-defined"):
484            if not isinstance(item, Element):
485                continue
486            # Read the values
487            name = item.get_attribute_string("meta:name")
488            if name is None:
489                continue
490            value = self._get_meta_value(item)
491            result[name] = value
492        return result
493
494    def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None:
495        """Return the content of the user defined metadata of that name.
496        Return None if no name matchs or a dic of fields.
497
498        Arguments:
499
500            name -- string, name (meta:name content)
501        """
502        result = {}
503        found = False
504        for item in self.get_elements("//meta:user-defined"):
505            if not isinstance(item, Element):
506                continue
507            # Read the values
508            name = item.get_attribute("meta:name")
509            if name == keyname:
510                found = True
511                break
512        if not found:
513            return None
514        result["name"] = name
515        value, value_type, text = self._get_meta_value(item, full=True)  # type: ignore
516        result["value"] = value
517        result["value_type"] = value_type
518        result["text"] = text
519        return result
520
521    def set_user_defined_metadata(self, name: str, value: Any) -> None:
522        if isinstance(value, bool):
523            value_type = "boolean"
524            value = "true" if value else "false"
525        elif isinstance(value, (int, float, Decimal)):
526            value_type = "float"
527            value = str(value)
528        elif isinstance(value, dtdate):
529            value_type = "date"
530            value = str(Date.encode(value))
531        elif isinstance(value, datetime):
532            value_type = "date"
533            value = str(DateTime.encode(value))
534        elif isinstance(value, str):
535            value_type = "string"
536        elif isinstance(value, timedelta):
537            value_type = "time"
538            value = str(Duration.encode(value))
539        else:
540            raise TypeError('unexpected type "%s" for value' % type(value))
541        # Already the same element ?
542        for metadata in self.get_elements("//meta:user-defined"):
543            if not isinstance(metadata, Element):
544                continue
545            if metadata.get_attribute("meta:name") == name:
546                break
547        else:
548            metadata = Element.from_tag("meta:user-defined")
549            metadata.set_attribute("meta:name", name)
550            self.get_meta_body().append(metadata)
551        metadata.set_attribute("meta:value-type", value_type)
552        metadata.text = value
553
554    def _get_meta_value(
555        self, element: Element, full: bool = False
556    ) -> Any | tuple[Any, str, str]:
557        """get_value() deicated to the meta data part, for one meta element."""
558        if full:
559            return self._get_meta_value_full(element)
560        else:
561            return self._get_meta_value_full(element)[0]
562
563    @staticmethod
564    def _get_meta_value_full(element: Element) -> tuple[Any, str, str]:
565        """get_value deicated to the meta data part, for one meta element."""
566        # name = element.get_attribute('meta:name')
567        value_type = element.get_attribute_string("meta:value-type")
568        if value_type is None:
569            value_type = "string"
570        text = element.text
571        # Interpretation
572        if value_type == "boolean":
573            return (Boolean.decode(text), value_type, text)
574        if value_type in ("float", "percentage", "currency"):
575            return (Decimal(text), value_type, text)
576        if value_type == "date":
577            if "T" in text:
578                return (DateTime.decode(text), value_type, text)
579            else:
580                return (Date.decode(text), value_type, text)
581        if value_type == "string":
582            return (text, value_type, text)
583        if value_type == "time":
584            return (Duration.decode(text), value_type, text)
585        raise TypeError(f"Unknown value type: '{value_type!r}'")

Representation of an XML part.

Abstraction of the XML library behind.

Meta(*args: Any, **kwargs: Any)
44    def __init__(self, *args: Any, **kwargs: Any) -> None:
45        super().__init__(*args, **kwargs)
46        self._generator_modified: bool = False
def get_meta_body(self) -> Element:
48    def get_meta_body(self) -> Element:
49        return self.get_element("//office:meta")
def get_title(self) -> str | None:
51    def get_title(self) -> str | None:
52        """Get the title of the document.
53
54        This is not the first heading but the title metadata.
55
56        Return: str (or None if inexistant)
57        """
58        element = self.get_element("//dc:title")
59        if element is None:
60            return None
61        return element.text

Get the title of the document.

This is not the first heading but the title metadata.

Return: str (or None if inexistant)

def set_title(self, title: str) -> None:
63    def set_title(self, title: str) -> None:
64        """Set the title of the document.
65
66        This is not the first heading but the title metadata.
67
68        Arguments:
69
70            title -- str
71        """
72        element = self.get_element("//dc:title")
73        if element is None:
74            element = Element.from_tag("dc:title")
75            self.get_meta_body().append(element)
76        element.text = title

Set the title of the document.

This is not the first heading but the title metadata.

Arguments:

title -- str
def get_description(self) -> str | None:
78    def get_description(self) -> str | None:
79        """Get the description of the document. Also known as comments.
80
81        Return: str (or None if inexistant)
82        """
83        element = self.get_element("//dc:description")
84        if element is None:
85            return None
86        return element.text

Get the description of the document. Also known as comments.

Return: str (or None if inexistant)

def get_comments(self) -> str | None:
78    def get_description(self) -> str | None:
79        """Get the description of the document. Also known as comments.
80
81        Return: str (or None if inexistant)
82        """
83        element = self.get_element("//dc:description")
84        if element is None:
85            return None
86        return element.text

Get the description of the document. Also known as comments.

Return: str (or None if inexistant)

def set_description(self, description: str) -> None:
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description

Set the description of the document. Also known as comments.

Arguments:

description -- str
def set_comments(self, description: str) -> None:
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description

Set the description of the document. Also known as comments.

Arguments:

description -- str
def get_subject(self) -> str | None:
106    def get_subject(self) -> str | None:
107        """Get the subject of the document.
108
109        Return: str (or None if inexistant)
110        """
111        element = self.get_element("//dc:subject")
112        if element is None:
113            return None
114        return element.text

Get the subject of the document.

Return: str (or None if inexistant)

def set_subject(self, subject: str) -> None:
116    def set_subject(self, subject: str) -> None:
117        """Set the subject of the document.
118
119        Arguments:
120
121            subject -- str
122        """
123        element = self.get_element("//dc:subject")
124        if element is None:
125            element = Element.from_tag("dc:subject")
126            self.get_meta_body().append(element)
127        element.text = subject

Set the subject of the document.

Arguments:

subject -- str
def get_language(self) -> str | None:
129    def get_language(self) -> str | None:
130        """Get the language code of the document.
131
132        Return: str (or None if inexistant)
133
134        Example::
135
136            >>> document.meta.get_language()
137            fr-FR
138        """
139        element = self.get_element("//dc:language")
140        if element is None:
141            return None
142        return element.text

Get the language code of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_language()
fr-FR
def set_language(self, language: str) -> None:
144    def set_language(self, language: str) -> None:
145        """Set the language code of the document.
146
147        Arguments:
148
149            language -- str
150
151        Example::
152
153            >>> document.meta.set_language('fr-FR')
154        """
155        language = str(language)
156        if not self._is_RFC3066(language):
157            raise TypeError(
158                'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)'
159            )
160        element = self.get_element("//dc:language")
161        if element is None:
162            element = Element.from_tag("dc:language")
163            self.get_meta_body().append(element)
164        element.text = language

Set the language code of the document.

Arguments:

language -- str

Example::

>>> document.meta.set_language('fr-FR')
def get_modification_date(self) -> datetime.datetime | None:
187    def get_modification_date(self) -> datetime | None:
188        """Get the last modified date of the document.
189
190        Return: datetime (or None if inexistant)
191        """
192        element = self.get_element("//dc:date")
193        if element is None:
194            return None
195        modification_date = element.text
196        return DateTime.decode(modification_date)

Get the last modified date of the document.

Return: datetime (or None if inexistant)

def set_modification_date(self, date: datetime.datetime) -> None:
198    def set_modification_date(self, date: datetime) -> None:
199        """Set the last modified date of the document.
200
201        Arguments:
202
203            date -- datetime
204        """
205        element = self.get_element("//dc:date")
206        if element is None:
207            element = Element.from_tag("dc:date")
208            self.get_meta_body().append(element)
209        element.text = DateTime.encode(date)

Set the last modified date of the document.

Arguments:

date -- datetime
def get_creation_date(self) -> datetime.datetime | None:
211    def get_creation_date(self) -> datetime | None:
212        """Get the creation date of the document.
213
214        Return: datetime (or None if inexistant)
215        """
216        element = self.get_element("//meta:creation-date")
217        if element is None:
218            return None
219        creation_date = element.text
220        return DateTime.decode(creation_date)

Get the creation date of the document.

Return: datetime (or None if inexistant)

def set_creation_date(self, date: datetime.datetime) -> None:
222    def set_creation_date(self, date: datetime) -> None:
223        """Set the creation date of the document.
224
225        Arguments:
226
227            date -- datetime
228        """
229        element = self.get_element("//meta:creation-date")
230        if element is None:
231            element = Element.from_tag("meta:creation-date")
232            self.get_meta_body().append(element)
233        element.text = DateTime.encode(date)

Set the creation date of the document.

Arguments:

date -- datetime
def get_initial_creator(self) -> str | None:
235    def get_initial_creator(self) -> str | None:
236        """Get the first creator of the document.
237
238        Return: str (or None if inexistant)
239
240        Example::
241
242            >>> document.meta.get_initial_creator()
243            Unknown
244        """
245        element = self.get_element("//meta:initial-creator")
246        if element is None:
247            return None
248        return element.text

Get the first creator of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_initial_creator()
Unknown
def set_initial_creator(self, creator: str) -> None:
250    def set_initial_creator(self, creator: str) -> None:
251        """Set the first creator of the document.
252
253        Arguments:
254
255            creator -- str
256
257        Example::
258
259            >>> document.meta.set_initial_creator("Plato")
260        """
261        element = self.get_element("//meta:initial-creator")
262        if element is None:
263            element = Element.from_tag("meta:initial-creator")
264            self.get_meta_body().append(element)
265        element.text = creator

Set the first creator of the document.

Arguments:

creator -- str

Example::

>>> document.meta.set_initial_creator("Plato")
def get_creator(self) -> str | None:
267    def get_creator(self) -> str | None:
268        """Get the creator of the document.
269
270        Return: str (or None if inexistant)
271
272        Example::
273
274            >>> document.meta.get_creator()
275            Unknown
276        """
277        element = self.get_element("//dc:creator")
278        if element is None:
279            return None
280        return element.text

Get the creator of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_creator()
Unknown
def set_creator(self, creator: str) -> None:
282    def set_creator(self, creator: str) -> None:
283        """Set the creator of the document.
284
285        Arguments:
286
287            creator -- str
288
289        Example::
290
291            >>> document.meta.set_creator("Plato")
292        """
293        element = self.get_element("//dc:creator")
294        if element is None:
295            element = Element.from_tag("dc:creator")
296            self.get_meta_body().append(element)
297        element.text = creator

Set the creator of the document.

Arguments:

creator -- str

Example::

>>> document.meta.set_creator("Plato")
def get_keywords(self) -> str | None:
299    def get_keywords(self) -> str | None:
300        """Get the keywords of the document. Return the field as-is, without
301        any assumption on the keyword separator.
302
303        Return: str (or None if inexistant)
304        """
305        element = self.get_element("//meta:keyword")
306        if element is None:
307            return None
308        return element.text

Get the keywords of the document. Return the field as-is, without any assumption on the keyword separator.

Return: str (or None if inexistant)

def set_keywords(self, keywords: str) -> None:
310    def set_keywords(self, keywords: str) -> None:
311        """Set the keywords of the document. Although the name is plural, a
312        str string is required, so join your list first.
313
314        Arguments:
315
316            keywords -- str
317        """
318        element = self.get_element("//meta:keyword")
319        if element is None:
320            element = Element.from_tag("meta:keyword")
321            self.get_meta_body().append(element)
322        element.text = keywords

Set the keywords of the document. Although the name is plural, a str string is required, so join your list first.

Arguments:

keywords -- str
def get_editing_duration(self) -> datetime.timedelta | None:
324    def get_editing_duration(self) -> timedelta | None:
325        """Get the time the document was edited, as reported by the
326        generator.
327
328        Return: timedelta (or None if inexistant)
329        """
330        element = self.get_element("//meta:editing-duration")
331        if element is None:
332            return None
333        duration = element.text
334        return Duration.decode(duration)

Get the time the document was edited, as reported by the generator.

Return: timedelta (or None if inexistant)

def set_editing_duration(self, duration: datetime.timedelta) -> None:
336    def set_editing_duration(self, duration: timedelta) -> None:
337        """Set the time the document was edited.
338
339        Arguments:
340
341            duration -- timedelta
342        """
343        if not isinstance(duration, timedelta):
344            raise TypeError("duration must be a timedelta")
345        element = self.get_element("//meta:editing-duration")
346        if element is None:
347            element = Element.from_tag("meta:editing-duration")
348            self.get_meta_body().append(element)
349        element.text = Duration.encode(duration)

Set the time the document was edited.

Arguments:

duration -- timedelta
def get_editing_cycles(self) -> int | None:
351    def get_editing_cycles(self) -> int | None:
352        """Get the number of times the document was edited, as reported by
353        the generator.
354
355        Return: int (or None if inexistant)
356        """
357        element = self.get_element("//meta:editing-cycles")
358        if element is None:
359            return None
360        cycles = element.text
361        return int(cycles)

Get the number of times the document was edited, as reported by the generator.

Return: int (or None if inexistant)

def set_editing_cycles(self, cycles: int) -> None:
363    def set_editing_cycles(self, cycles: int) -> None:
364        """Set the number of times the document was edited.
365
366        Arguments:
367
368            cycles -- int
369        """
370        if not isinstance(cycles, int):
371            raise TypeError("cycles must be an int")
372        if cycles < 1:
373            raise ValueError("cycles must be a positive int")
374        element = self.get_element("//meta:editing-cycles")
375        if element is None:
376            element = Element.from_tag("meta:editing-cycles")
377            self.get_meta_body().append(element)
378        element.text = str(cycles)

Set the number of times the document was edited.

Arguments:

cycles -- int
def get_generator(self) -> str | None:
380    def get_generator(self) -> str | None:
381        """Get the signature of the software that generated this document.
382
383        Return: str (or None if inexistant)
384
385        Example::
386
387            >>> document.meta.get_generator()
388            KOffice/2.0.0
389        """
390        element = self.get_element("//meta:generator")
391        if element is None:
392            return None
393        return element.text

Get the signature of the software that generated this document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_generator()
KOffice/2.0.0
def set_generator(self, generator: str) -> None:
395    def set_generator(self, generator: str) -> None:
396        """Set the signature of the software that generated this document.
397
398        Arguments:
399
400            generator -- str
401
402        Example::
403
404            >>> document.meta.set_generator("Odfdo experiment")
405        """
406        element = self.get_element("//meta:generator")
407        if element is None:
408            element = Element.from_tag("meta:generator")
409            self.get_meta_body().append(element)
410        element.text = generator
411        self._generator_modified = True

Set the signature of the software that generated this document.

Arguments:

generator -- str

Example::

>>> document.meta.set_generator("Odfdo experiment")
def set_generator_default(self) -> None:
413    def set_generator_default(self) -> None:
414        """Set the signature of the software that generated this document
415        to ourself.
416
417        Example::
418
419            >>> document.meta.set_generator_default()
420        """
421        if not self._generator_modified:
422            self.set_generator(GENERATOR)

Set the signature of the software that generated this document to ourself.

Example::

>>> document.meta.set_generator_default()
def get_statistic(self) -> dict[str, int] | None:
424    def get_statistic(self) -> dict[str, int] | None:
425        """Get the statistic from the software that generated this document.
426
427        Return: dict (or None if inexistant)
428
429        Example::
430
431            >>> document.get_statistic():
432            {'meta:table-count': 1,
433             'meta:image-count': 2,
434             'meta:object-count': 3,
435             'meta:page-count': 4,
436             'meta:paragraph-count': 5,
437             'meta:word-count': 6,
438             'meta:character-count': 7}
439        """
440        element = self.get_element("//meta:document-statistic")
441        if element is None:
442            return None
443        statistic = {}
444        for key, value in element.attributes.items():
445            statistic[to_str(key)] = int(value)
446        return statistic

Get the statistic from the software that generated this document.

Return: dict (or None if inexistant)

Example::

>>> document.get_statistic():
{'meta:table-count': 1,
 'meta:image-count': 2,
 'meta:object-count': 3,
 'meta:page-count': 4,
 'meta:paragraph-count': 5,
 'meta:word-count': 6,
 'meta:character-count': 7}
def set_statistic(self, statistic: dict[str, int]) -> None:
448    def set_statistic(self, statistic: dict[str, int]) -> None:
449        """Set the statistic for the documents: number of words, paragraphs,
450        etc.
451
452        Arguments:
453
454            statistic -- dict
455
456        Example::
457
458            >>> statistic = {'meta:table-count': 1,
459                             'meta:image-count': 2,
460                             'meta:object-count': 3,
461                             'meta:page-count': 4,
462                             'meta:paragraph-count': 5,
463                             'meta:word-count': 6,
464                             'meta:character-count': 7}
465            >>> document.meta.set_statistic(statistic)
466        """
467        if not isinstance(statistic, dict):
468            raise TypeError("Statistic must be a dict")
469        element = self.get_element("//meta:document-statistic")
470        for key, value in statistic.items():
471            try:
472                ivalue = int(value)
473            except ValueError as e:
474                raise TypeError("Statistic value must be a int") from e
475            element.set_attribute(to_str(key), str(ivalue))

Set the statistic for the documents: number of words, paragraphs, etc.

Arguments:

statistic -- dict

Example::

>>> statistic = {'meta:table-count': 1,
                 'meta:image-count': 2,
                 'meta:object-count': 3,
                 'meta:page-count': 4,
                 'meta:paragraph-count': 5,
                 'meta:word-count': 6,
                 'meta:character-count': 7}
>>> document.meta.set_statistic(statistic)
def get_user_defined_metadata(self) -> dict[str, typing.Any]:
477    def get_user_defined_metadata(self) -> dict[str, Any]:
478        """Return a dict of str/value mapping.
479
480        Value types can be: Decimal, date, time, boolean or str.
481        """
482        result: dict[str, Any] = {}
483        for item in self.get_elements("//meta:user-defined"):
484            if not isinstance(item, Element):
485                continue
486            # Read the values
487            name = item.get_attribute_string("meta:name")
488            if name is None:
489                continue
490            value = self._get_meta_value(item)
491            result[name] = value
492        return result

Return a dict of str/value mapping.

Value types can be: Decimal, date, time, boolean or str.

def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, typing.Any] | None:
494    def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None:
495        """Return the content of the user defined metadata of that name.
496        Return None if no name matchs or a dic of fields.
497
498        Arguments:
499
500            name -- string, name (meta:name content)
501        """
502        result = {}
503        found = False
504        for item in self.get_elements("//meta:user-defined"):
505            if not isinstance(item, Element):
506                continue
507            # Read the values
508            name = item.get_attribute("meta:name")
509            if name == keyname:
510                found = True
511                break
512        if not found:
513            return None
514        result["name"] = name
515        value, value_type, text = self._get_meta_value(item, full=True)  # type: ignore
516        result["value"] = value
517        result["value_type"] = value_type
518        result["text"] = text
519        return result

Return the content of the user defined metadata of that name. Return None if no name matchs or a dic of fields.

Arguments:

name -- string, name (meta:name content)
def set_user_defined_metadata(self, name: str, value: Any) -> None:
521    def set_user_defined_metadata(self, name: str, value: Any) -> None:
522        if isinstance(value, bool):
523            value_type = "boolean"
524            value = "true" if value else "false"
525        elif isinstance(value, (int, float, Decimal)):
526            value_type = "float"
527            value = str(value)
528        elif isinstance(value, dtdate):
529            value_type = "date"
530            value = str(Date.encode(value))
531        elif isinstance(value, datetime):
532            value_type = "date"
533            value = str(DateTime.encode(value))
534        elif isinstance(value, str):
535            value_type = "string"
536        elif isinstance(value, timedelta):
537            value_type = "time"
538            value = str(Duration.encode(value))
539        else:
540            raise TypeError('unexpected type "%s" for value' % type(value))
541        # Already the same element ?
542        for metadata in self.get_elements("//meta:user-defined"):
543            if not isinstance(metadata, Element):
544                continue
545            if metadata.get_attribute("meta:name") == name:
546                break
547        else:
548            metadata = Element.from_tag("meta:user-defined")
549            metadata.set_attribute("meta:name", name)
550            self.get_meta_body().append(metadata)
551        metadata.set_attribute("meta:value-type", value_type)
552        metadata.text = value
NEXT_SIBLING = 2
class NamedRange(odfdo.Element):
2844class NamedRange(Element):
2845    """ODF Named Range "table:named-range". Identifies inside the spreadsheet
2846    a range of cells of a table by a name and the name of the table.
2847
2848    Name Ranges have the following attributes:
2849
2850        name -- name of the named range
2851
2852        table_name -- name of the table
2853
2854        start -- first cell of the named range, tuple (x, y)
2855
2856        end -- last cell of the named range, tuple (x, y)
2857
2858        crange -- range of the named range, tuple (x, y, z, t)
2859
2860        usage -- None or str, usage of the named range.
2861    """
2862
2863    _tag = "table:named-range"
2864
2865    def __init__(
2866        self,
2867        name: str | None = None,
2868        crange: str | tuple | list | None = None,
2869        table_name: str | None = None,
2870        usage: str | None = None,
2871        **kwargs: Any,
2872    ) -> None:
2873        """Create a Named Range element. 'name' must contains only letters, digits
2874           and '_', and must not be like a coordinate as 'A1'. 'table_name' must be
2875           a correct table name (no "'" or "/" in it).
2876
2877        Arguments:
2878
2879             name -- str, name of the named range
2880
2881             crange -- str or tuple of int, cell or area coordinate
2882
2883             table_name -- str, name of the table
2884
2885             usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2886        """
2887        super().__init__(**kwargs)
2888        self.usage = None
2889        if self._do_init:
2890            self.name = name or ""
2891            self.table_name = _table_name_check(table_name)
2892            self.set_range(crange or "")
2893            self.set_usage(usage)
2894        cell_range_address = self.get_attribute_string("table:cell-range-address") or ""
2895        if not cell_range_address:
2896            self.table_name = ""
2897            self.start = None
2898            self.end = None
2899            self.crange = None
2900            self.usage = None
2901            return
2902        self.usage = self.get_attribute("table:range-usable-as")
2903        name_range = cell_range_address.replace("$", "")
2904        name, crange = name_range.split(".", 1)
2905        if name.startswith("'") and name.endswith("'"):
2906            name = name[1:-1]
2907        self.table_name = name
2908        crange = crange.replace(".", "")
2909        self._set_range(crange)
2910
2911    def set_usage(self, usage: str | None = None) -> None:
2912        """Set the usage of the Named Range. Usage can be None (default) or one
2913        of :
2914            'print-range'
2915            'filter'
2916            'repeat-column'
2917            'repeat-row'
2918
2919        Arguments:
2920
2921            usage -- None or str
2922        """
2923        if usage is not None:
2924            usage = usage.strip().lower()
2925            if usage not in ("print-range", "filter", "repeat-column", "repeat-row"):
2926                usage = None
2927        if usage is None:
2928            with contextlib.suppress(KeyError):
2929                self.del_attribute("table:range-usable-as")
2930            self.usage = None
2931        else:
2932            self.set_attribute("table:range-usable-as", usage)
2933            self.usage = usage
2934
2935    @property
2936    def name(self) -> str | None:
2937        """Get / set the name of the table."""
2938        return self.get_attribute_string("table:name")
2939
2940    @name.setter
2941    def name(self, name: str) -> None:
2942        """Set the name of the Named Range. The name is mandatory, if a Named
2943        Range of the same name exists, it will be replaced. Name must contains
2944        only alphanumerics characters and '_', and can not be of a cell
2945        coordinates form like 'AB12'.
2946
2947        Arguments:
2948
2949            name -- str
2950        """
2951        name = name.strip()
2952        if not name:
2953            raise ValueError("Name required.")
2954        for x in name:
2955            if x in forbidden_in_named_range():
2956                raise ValueError(f"Character forbidden '{x}' ")
2957        step = ""
2958        for x in name:
2959            if x in string.ascii_letters and step in ("", "A"):
2960                step = "A"
2961                continue
2962            elif step in ("A", "A1") and x in string.digits:
2963                step = "A1"
2964                continue
2965            else:
2966                step = ""
2967                break
2968        if step == "A1":
2969            raise ValueError("Name of the type 'ABC123' is not allowed.")
2970        with contextlib.suppress(Exception):
2971            # we are not on an inserted in a document.
2972            body = self.document_body
2973            named_range = body.get_named_range(name)  # type: ignore
2974            if named_range:
2975                named_range.delete()
2976        self.set_attribute("table:name", name)
2977
2978    def set_table_name(self, name: str) -> None:
2979        """Set the name of the table of the Named Range. The name is mandatory.
2980
2981        Arguments:
2982
2983            name -- str
2984        """
2985        self.table_name = _table_name_check(name)
2986        self._update_attributes()
2987
2988    def _set_range(self, coord: tuple | list | str) -> None:
2989        digits = convert_coordinates(coord)
2990        if len(digits) == 4:
2991            x, y, z, t = digits
2992        else:
2993            x, y = digits
2994            z, t = digits
2995        self.start = x, y  # type: ignore
2996        self.end = z, t  # type: ignore
2997        self.crange = x, y, z, t  # type: ignore
2998
2999    def set_range(self, crange: str | tuple | list) -> None:
3000        """Set the range of the named range. Range can be either one cell
3001        (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric
3002        value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
3003
3004        Arguments:
3005
3006            crange -- str or tuple of int, cell or area coordinate
3007        """
3008        self._set_range(crange)
3009        self._update_attributes()
3010
3011    def _update_attributes(self) -> None:
3012        self.set_attribute("table:base-cell-address", self._make_base_cell_address())
3013        self.set_attribute("table:cell-range-address", self._make_cell_range_address())
3014
3015    def _make_base_cell_address(self) -> str:
3016        # assuming we got table_name and range
3017        if " " in self.table_name:
3018            name = f"'{self.table_name}'"
3019        else:
3020            name = self.table_name
3021        return f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}"  # type: ignore
3022
3023    def _make_cell_range_address(self) -> str:
3024        # assuming we got table_name and range
3025        if " " in self.table_name:
3026            name = f"'{self.table_name}'"
3027        else:
3028            name = self.table_name
3029        if self.start == self.end:
3030            return self._make_base_cell_address()
3031        return (
3032            f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}:"  # type: ignore
3033            f".${digit_to_alpha(self.end[0])}${self.end[1] + 1}"  # type: ignore
3034        )
3035
3036    def get_values(
3037        self,
3038        cell_type: str | None = None,
3039        complete: bool = True,
3040        get_type: bool = False,
3041        flat: bool = False,
3042    ) -> list:
3043        """Shortcut to retrieve the values of the cells of the named range. See
3044        table.get_values() for the arguments description and return format.
3045        """
3046        body = self.document_body
3047        if not body:
3048            raise ValueError("Table is not inside a document.")
3049        table = body.get_table(name=self.table_name)
3050        if table is None:
3051            raise ValueError
3052        return table.get_values(self.crange, cell_type, complete, get_type, flat)  # type: ignore
3053
3054    def get_value(self, get_type: bool = False) -> Any:
3055        """Shortcut to retrieve the value of the first cell of the named range.
3056        See table.get_value() for the arguments description and return format.
3057        """
3058        body = self.document_body
3059        if not body:
3060            raise ValueError("Table is not inside a document.")
3061        table = body.get_table(name=self.table_name)
3062        if table is None:
3063            raise ValueError
3064        return table.get_value(self.start, get_type)  # type: ignore
3065
3066    def set_values(
3067        self,
3068        values: list,
3069        style: str | None = None,
3070        cell_type: str | None = None,
3071        currency: str | None = None,
3072    ) -> None:
3073        """Shortcut to set the values of the cells of the named range.
3074        See table.set_values() for the arguments description.
3075        """
3076        body = self.document_body
3077        if not body:
3078            raise ValueError("Table is not inside a document.")
3079        table = body.get_table(name=self.table_name)
3080        if table is None:
3081            raise ValueError
3082        table.set_values(  # type: ignore
3083            values,
3084            coord=self.crange,
3085            style=style,
3086            cell_type=cell_type,
3087            currency=currency,
3088        )
3089
3090    def set_value(
3091        self,
3092        value: Any,
3093        cell_type: str | None = None,
3094        currency: str | None = None,
3095        style: str | None = None,
3096    ) -> None:
3097        """Shortcut to set the value of the first cell of the named range.
3098        See table.set_value() for the arguments description.
3099        """
3100        body = self.document_body
3101        if not body:
3102            raise ValueError("Table is not inside a document.")
3103        table = body.get_table(name=self.table_name)
3104        if table is None:
3105            raise ValueError
3106        table.set_value(  # type: ignore
3107            coord=self.start,
3108            value=value,
3109            cell_type=cell_type,
3110            currency=currency,
3111            style=style,
3112        )

ODF Named Range "table:named-range". Identifies inside the spreadsheet a range of cells of a table by a name and the name of the table.

Name Ranges have the following attributes:

name -- name of the named range

table_name -- name of the table

start -- first cell of the named range, tuple (x, y)

end -- last cell of the named range, tuple (x, y)

crange -- range of the named range, tuple (x, y, z, t)

usage -- None or str, usage of the named range.
NamedRange( name: str | None = None, crange: str | tuple | list | None = None, table_name: str | None = None, usage: str | None = None, **kwargs: Any)
2865    def __init__(
2866        self,
2867        name: str | None = None,
2868        crange: str | tuple | list | None = None,
2869        table_name: str | None = None,
2870        usage: str | None = None,
2871        **kwargs: Any,
2872    ) -> None:
2873        """Create a Named Range element. 'name' must contains only letters, digits
2874           and '_', and must not be like a coordinate as 'A1'. 'table_name' must be
2875           a correct table name (no "'" or "/" in it).
2876
2877        Arguments:
2878
2879             name -- str, name of the named range
2880
2881             crange -- str or tuple of int, cell or area coordinate
2882
2883             table_name -- str, name of the table
2884
2885             usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2886        """
2887        super().__init__(**kwargs)
2888        self.usage = None
2889        if self._do_init:
2890            self.name = name or ""
2891            self.table_name = _table_name_check(table_name)
2892            self.set_range(crange or "")
2893            self.set_usage(usage)
2894        cell_range_address = self.get_attribute_string("table:cell-range-address") or ""
2895        if not cell_range_address:
2896            self.table_name = ""
2897            self.start = None
2898            self.end = None
2899            self.crange = None
2900            self.usage = None
2901            return
2902        self.usage = self.get_attribute("table:range-usable-as")
2903        name_range = cell_range_address.replace("$", "")
2904        name, crange = name_range.split(".", 1)
2905        if name.startswith("'") and name.endswith("'"):
2906            name = name[1:-1]
2907        self.table_name = name
2908        crange = crange.replace(".", "")
2909        self._set_range(crange)

Create a Named Range element. 'name' must contains only letters, digits and '_', and must not be like a coordinate as 'A1'. 'table_name' must be a correct table name (no "'" or "/" in it).

Arguments:

 name -- str, name of the named range

 crange -- str or tuple of int, cell or area coordinate

 table_name -- str, name of the table

 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
usage
table_name
def set_usage(self, usage: str | None = None) -> None:
2911    def set_usage(self, usage: str | None = None) -> None:
2912        """Set the usage of the Named Range. Usage can be None (default) or one
2913        of :
2914            'print-range'
2915            'filter'
2916            'repeat-column'
2917            'repeat-row'
2918
2919        Arguments:
2920
2921            usage -- None or str
2922        """
2923        if usage is not None:
2924            usage = usage.strip().lower()
2925            if usage not in ("print-range", "filter", "repeat-column", "repeat-row"):
2926                usage = None
2927        if usage is None:
2928            with contextlib.suppress(KeyError):
2929                self.del_attribute("table:range-usable-as")
2930            self.usage = None
2931        else:
2932            self.set_attribute("table:range-usable-as", usage)
2933            self.usage = usage

Set the usage of the Named Range. Usage can be None (default) or one of : 'print-range' 'filter' 'repeat-column' 'repeat-row'

Arguments:

usage -- None or str
name: str | None
2935    @property
2936    def name(self) -> str | None:
2937        """Get / set the name of the table."""
2938        return self.get_attribute_string("table:name")

Get / set the name of the table.

def set_table_name(self, name: str) -> None:
2978    def set_table_name(self, name: str) -> None:
2979        """Set the name of the table of the Named Range. The name is mandatory.
2980
2981        Arguments:
2982
2983            name -- str
2984        """
2985        self.table_name = _table_name_check(name)
2986        self._update_attributes()

Set the name of the table of the Named Range. The name is mandatory.

Arguments:

name -- str
def set_range(self, crange: str | tuple | list) -> None:
2999    def set_range(self, crange: str | tuple | list) -> None:
3000        """Set the range of the named range. Range can be either one cell
3001        (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric
3002        value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
3003
3004        Arguments:
3005
3006            crange -- str or tuple of int, cell or area coordinate
3007        """
3008        self._set_range(crange)
3009        self._update_attributes()

Set the range of the named range. Range can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).

Arguments:

crange -- str or tuple of int, cell or area coordinate
def get_values( self, cell_type: str | None = None, complete: bool = True, get_type: bool = False, flat: bool = False) -> list:
3036    def get_values(
3037        self,
3038        cell_type: str | None = None,
3039        complete: bool = True,
3040        get_type: bool = False,
3041        flat: bool = False,
3042    ) -> list:
3043        """Shortcut to retrieve the values of the cells of the named range. See
3044        table.get_values() for the arguments description and return format.
3045        """
3046        body = self.document_body
3047        if not body:
3048            raise ValueError("Table is not inside a document.")
3049        table = body.get_table(name=self.table_name)
3050        if table is None:
3051            raise ValueError
3052        return table.get_values(self.crange, cell_type, complete, get_type, flat)  # type: ignore

Shortcut to retrieve the values of the cells of the named range. See table.get_values() for the arguments description and return format.

def get_value(self, get_type: bool = False) -> Any:
3054    def get_value(self, get_type: bool = False) -> Any:
3055        """Shortcut to retrieve the value of the first cell of the named range.
3056        See table.get_value() for the arguments description and return format.
3057        """
3058        body = self.document_body
3059        if not body:
3060            raise ValueError("Table is not inside a document.")
3061        table = body.get_table(name=self.table_name)
3062        if table is None:
3063            raise ValueError
3064        return table.get_value(self.start, get_type)  # type: ignore

Shortcut to retrieve the value of the first cell of the named range. See table.get_value() for the arguments description and return format.

def set_values( self, values: list, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
3066    def set_values(
3067        self,
3068        values: list,
3069        style: str | None = None,
3070        cell_type: str | None = None,
3071        currency: str | None = None,
3072    ) -> None:
3073        """Shortcut to set the values of the cells of the named range.
3074        See table.set_values() for the arguments description.
3075        """
3076        body = self.document_body
3077        if not body:
3078            raise ValueError("Table is not inside a document.")
3079        table = body.get_table(name=self.table_name)
3080        if table is None:
3081            raise ValueError
3082        table.set_values(  # type: ignore
3083            values,
3084            coord=self.crange,
3085            style=style,
3086            cell_type=cell_type,
3087            currency=currency,
3088        )

Shortcut to set the values of the cells of the named range. See table.set_values() for the arguments description.

def set_value( self, value: Any, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
3090    def set_value(
3091        self,
3092        value: Any,
3093        cell_type: str | None = None,
3094        currency: str | None = None,
3095        style: str | None = None,
3096    ) -> None:
3097        """Shortcut to set the value of the first cell of the named range.
3098        See table.set_value() for the arguments description.
3099        """
3100        body = self.document_body
3101        if not body:
3102            raise ValueError("Table is not inside a document.")
3103        table = body.get_table(name=self.table_name)
3104        if table is None:
3105            raise ValueError
3106        table.set_value(  # type: ignore
3107            coord=self.start,
3108            value=value,
3109            cell_type=cell_type,
3110            currency=currency,
3111            style=style,
3112        )

Shortcut to set the value of the first cell of the named range. See table.set_value() for the arguments description.

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Note(odfdo.Element):
 57class Note(Element):
 58    """Either a footnote or a endnote element with the given text,
 59    optionally referencing it using the given note_id.
 60
 61    Arguments:
 62
 63        note_class -- 'footnote' or 'endnote'
 64
 65        note_id -- str
 66
 67        citation -- str
 68
 69        body -- str or Element
 70    """
 71
 72    _tag = "text:note"
 73    _properties = (
 74        PropDef("note_class", "text:note-class"),
 75        PropDef("note_id", "text:id"),
 76    )
 77
 78    def __init__(
 79        self,
 80        note_class: str = "footnote",
 81        note_id: str | None = None,
 82        citation: str | None = None,
 83        body: str | None = None,
 84        **kwargs: Any,
 85    ) -> None:
 86        super().__init__(**kwargs)
 87        if self._do_init:
 88            self.insert(Element.from_tag("text:note-body"), position=0)
 89            self.insert(Element.from_tag("text:note-citation"), position=0)
 90            self.note_class = note_class
 91            if note_id is not None:
 92                self.note_id = note_id
 93            if citation is not None:
 94                self.citation = citation
 95            if body is not None:
 96                self.note_body = body
 97
 98    @property
 99    def citation(self) -> str:
100        note_citation = self.get_element("text:note-citation")
101        if note_citation:
102            return note_citation.text
103        return ""
104
105    @citation.setter
106    def citation(self, text: str | None) -> None:
107        note_citation = self.get_element("text:note-citation")
108        if note_citation:
109            note_citation.text = text  # type:ignore
110
111    @property
112    def note_body(self) -> str:
113        note_body = self.get_element("text:note-body")
114        if note_body:
115            return note_body.text_content
116        return ""
117
118    @note_body.setter
119    def note_body(self, text_or_element: Element | str | None) -> None:
120        note_body = self.get_element("text:note-body")
121        if not note_body:
122            return None
123        if text_or_element is None:
124            note_body.text_content = ""
125        elif isinstance(text_or_element, str):
126            note_body.text_content = text_or_element
127        elif isinstance(text_or_element, Element):
128            note_body.clear()
129            note_body.append(text_or_element)
130        else:
131            raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"')
132
133    def check_validity(self) -> None:
134        if not self.note_class:
135            raise ValueError('Note class must be "footnote" or "endnote"')
136        if not self.note_id:
137            raise ValueError("Note must have an id")
138        if not self.citation:
139            raise ValueError("Note must have a citation")
140        if not self.note_body:
141            pass

Either a footnote or a endnote element with the given text, optionally referencing it using the given note_id.

Arguments:

note_class -- 'footnote' or 'endnote'

note_id -- str

citation -- str

body -- str or Element
Note( note_class: str = 'footnote', note_id: str | None = None, citation: str | None = None, body: str | None = None, **kwargs: Any)
78    def __init__(
79        self,
80        note_class: str = "footnote",
81        note_id: str | None = None,
82        citation: str | None = None,
83        body: str | None = None,
84        **kwargs: Any,
85    ) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.insert(Element.from_tag("text:note-body"), position=0)
89            self.insert(Element.from_tag("text:note-citation"), position=0)
90            self.note_class = note_class
91            if note_id is not None:
92                self.note_id = note_id
93            if citation is not None:
94                self.citation = citation
95            if body is not None:
96                self.note_body = body
citation: str
 98    @property
 99    def citation(self) -> str:
100        note_citation = self.get_element("text:note-citation")
101        if note_citation:
102            return note_citation.text
103        return ""
note_body: str
111    @property
112    def note_body(self) -> str:
113        note_body = self.get_element("text:note-body")
114        if note_body:
115            return note_body.text_content
116        return ""
def check_validity(self) -> None:
133    def check_validity(self) -> None:
134        if not self.note_class:
135            raise ValueError('Note class must be "footnote" or "endnote"')
136        if not self.note_id:
137            raise ValueError("Note must have an id")
138        if not self.citation:
139            raise ValueError("Note must have a citation")
140        if not self.note_body:
141            pass
note_class: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
note_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
PREV_SIBLING = 3
def PageBreak() -> Paragraph:
831def PageBreak() -> Paragraph:
832    """Return an empty paragraph with a manual page break.
833
834    Using this function requires to register the page break style with:
835        document.add_page_break_style()
836    """
837    return Paragraph("", style="odfdopagebreak")

Return an empty paragraph with a manual page break.

Using this function requires to register the page break style with: document.add_page_break_style()

class Paragraph(odfdo.paragraph_base.ParagraphBase):
147class Paragraph(ParagraphBase):
148    """Specialised element for paragraphs "text:p". The "text:p" element
149    represents a paragraph, which is the basic unit of text in an OpenDocument
150    file.
151    """
152
153    _tag = "text:p"
154
155    def __init__(
156        self,
157        text_or_element: str | Element | None = None,
158        style: str | None = None,
159        **kwargs: Any,
160    ):
161        """Create a paragraph element of the given style containing the optional
162        given text.
163
164        Arguments:
165
166            text -- str or Element
167
168            style -- str
169        """
170        super().__init__(**kwargs)
171        if self._do_init:
172            if isinstance(text_or_element, Element):
173                self.append(text_or_element)
174            else:
175                self.text = text_or_element  # type:ignore
176            if style is not None:
177                self.style = style
178
179    def insert_note(
180        self,
181        note_element: Note | None = None,
182        after: str | Element | None = None,
183        note_class: str = "footnote",
184        note_id: str | None = None,
185        citation: str | None = None,
186        body: str | None = None,
187    ) -> None:
188        if note_element is None:
189            note_element = Note(
190                note_class=note_class, note_id=note_id, citation=citation, body=body
191            )
192        else:
193            # XXX clone or modify the argument?
194            if note_class:
195                note_element.note_class = note_class
196            if note_id:
197                note_element.note_id = note_id
198            if citation:
199                note_element.citation = citation
200            if body:
201                note_element.note_body = body
202        note_element.check_validity()
203        if isinstance(after, str):
204            self._insert(note_element, after=after, main_text=True)
205        elif isinstance(after, Element):
206            after.insert(note_element, FIRST_CHILD)
207        else:
208            self.insert(note_element, FIRST_CHILD)
209
210    def insert_annotation(  # noqa: C901
211        self,
212        annotation_element: Annotation | None = None,
213        before: str | None = None,
214        after: str | Element | None = None,
215        position: int | tuple = 0,
216        content: str | Element | None = None,
217        body: str | None = None,
218        creator: str | None = None,
219        date: datetime | None = None,
220    ) -> Annotation:
221        """Insert an annotation, at the position defined by the regex (before,
222        after, content) or by positionnal argument (position). If content is
223        provided, the annotation covers the full content regex. Else, the
224        annotation is positionned either 'before' or 'after' provided regex.
225
226        If content is an odf element (ie: paragraph, span, ...), the full inner
227        content is covered by the annotation (of the position just after if
228        content is a single empty tag).
229
230        If content/before or after exists (regex) and return a group of matching
231        positions, the position value is the index of matching place to use.
232
233        annotation_element can contain a previously created annotation, else
234        the annotation is created from the body, creator and optional date
235        (current date by default).
236
237        Arguments:
238
239            annotation_element -- Annotation or None
240
241            before -- str regular expression or None
242
243            after -- str regular expression or Element or None
244
245            content -- str regular expression or None, or Element
246
247            position -- int or tuple of int
248
249            body -- str or Element
250
251            creator -- str
252
253            date -- datetime
254        """
255
256        if annotation_element is None:
257            annotation_element = Annotation(
258                text_or_element=body, creator=creator, date=date, parent=self
259            )
260        else:
261            # XXX clone or modify the argument?
262            if body:
263                annotation_element.note_body = body
264            if creator:
265                annotation_element.dc_creator = creator
266            if date:
267                annotation_element.dc_date = date
268        annotation_element.check_validity()
269
270        # special case: content is an odf element (ie: a paragraph)
271        if isinstance(content, Element):
272            if content.is_empty():
273                content.insert(annotation_element, xmlposition=NEXT_SIBLING)
274                return annotation_element
275            content.insert(annotation_element, start=True)
276            annotation_end = AnnotationEnd(annotation_element)
277            content.append(annotation_end)
278            return annotation_element
279
280        # special case
281        if isinstance(after, Element):
282            after.insert(annotation_element, FIRST_CHILD)
283            return annotation_element
284
285        # With "content" => automatically insert a "start" and an "end"
286        # bookmark
287        if (
288            before is None
289            and after is None
290            and content is not None
291            and isinstance(position, int)
292        ):
293            # Start tag
294            self._insert(
295                annotation_element, before=content, position=position, main_text=True
296            )
297            # End tag
298            annotation_end = AnnotationEnd(annotation_element)
299            self._insert(
300                annotation_end, after=content, position=position, main_text=True
301            )
302            return annotation_element
303
304        # With "(int, int)" =>  automatically insert a "start" and an "end"
305        # bookmark
306        if (
307            before is None
308            and after is None
309            and content is None
310            and isinstance(position, tuple)
311        ):
312            # Start
313            self._insert(annotation_element, position=position[0], main_text=True)
314            # End
315            annotation_end = AnnotationEnd(annotation_element)
316            self._insert(annotation_end, position=position[1], main_text=True)
317            return annotation_element
318
319        # Without "content" nor "position"
320        if content is not None or not isinstance(position, int):
321            raise ValueError("Bad arguments")
322
323        # Insert
324        self._insert(
325            annotation_element,
326            before=before,
327            after=after,
328            position=position,
329            main_text=True,
330        )
331        return annotation_element
332
333    def insert_annotation_end(
334        self,
335        annotation_element: Annotation,
336        before: str | None = None,
337        after: str | None = None,
338        position: int = 0,
339    ) -> AnnotationEnd:
340        """Insert an annotation end tag for an existing annotation. If some end
341        tag already exists, replace it. Annotation end tag is set at the
342        position defined by the regex (before or after).
343
344        If content/before or after (regex) returns a group of matching
345        positions, the position value is the index of matching place to use.
346
347        Arguments:
348
349            annotation_element -- Annotation (mandatory)
350
351            before -- str regular expression or None
352
353            after -- str regular expression or None
354
355            position -- int
356        """
357
358        if annotation_element is None:
359            raise ValueError
360        if not isinstance(annotation_element, Annotation):
361            raise TypeError("Not a <office:annotation> Annotation")
362
363        # remove existing end tag
364        name = annotation_element.name
365        existing_end_tag = self.get_annotation_end(name=name)
366        if existing_end_tag:
367            existing_end_tag.delete()
368
369        # create the end tag
370        end_tag = AnnotationEnd(annotation_element)
371
372        # Insert
373        self._insert(
374            end_tag, before=before, after=after, position=position, main_text=True
375        )
376        return end_tag
377
378    def set_reference_mark(
379        self,
380        name: str,
381        before: str | None = None,
382        after: str | None = None,
383        position: int = 0,
384        content: str | Element | None = None,
385    ) -> Element:
386        """Insert a reference mark, at the position defined by the regex
387        (before, after, content) or by positionnal argument (position). If
388        content is provided, the annotation covers the full range content regex
389        (instances of ReferenceMarkStart and ReferenceMarkEnd are
390        created). Else, an instance of ReferenceMark is positionned either
391        'before' or 'after' provided regex.
392
393        If content is an ODF Element (ie: Paragraph, Span, ...), the full inner
394        content is referenced (of the position just after if content is a single
395        empty tag).
396
397        If content/before or after exists (regex) and return a group of matching
398        positions, the position value is the index of matching place to use.
399
400        Name is mandatory and shall be unique in the document for the preference
401        mark range.
402
403        Arguments:
404
405            name -- str
406
407            before -- str regular expression or None
408
409            after -- str regular expression or None,
410
411            content -- str regular expression or None, or Element
412
413            position -- int or tuple of int
414
415        Return: the created ReferenceMark or ReferenceMarkStart
416        """
417        # special case: content is an odf element (ie: a paragraph)
418        if isinstance(content, Element):
419            if content.is_empty():
420                reference = ReferenceMark(name)
421                content.insert(reference, xmlposition=NEXT_SIBLING)
422                return reference
423            reference_start = ReferenceMarkStart(name)
424            content.insert(reference_start, start=True)
425            reference_end = ReferenceMarkEnd(name)
426            content.append(reference_end)
427            return reference_start
428
429        # With "content" => automatically insert a "start" and an "end"
430        # reference
431        if (
432            before is None
433            and after is None
434            and content is not None
435            and isinstance(position, int)
436        ):
437            # Start tag
438            reference_start = ReferenceMarkStart(name)
439            self._insert(
440                reference_start, before=content, position=position, main_text=True
441            )
442            # End tag
443            reference_end = ReferenceMarkEnd(name)
444            self._insert(
445                reference_end, after=content, position=position, main_text=True
446            )
447            return reference_start
448
449        # With "(int, int)" =>  automatically insert a "start" and an "end"
450        if (
451            before is None
452            and after is None
453            and content is None
454            and isinstance(position, tuple)
455        ):
456            # Start
457            reference_start = ReferenceMarkStart(name)
458            self._insert(reference_start, position=position[0], main_text=True)
459            # End
460            reference_end = ReferenceMarkEnd(name)
461            self._insert(reference_end, position=position[1], main_text=True)
462            return reference_start
463
464        # Without "content" nor "position"
465        if content is not None or not isinstance(position, int):
466            raise ValueError("bad arguments")
467
468        # Insert a positional reference mark
469        reference = ReferenceMark(name)
470        self._insert(
471            reference,
472            before=before,
473            after=after,
474            position=position,
475            main_text=True,
476        )
477        return reference
478
479    def set_reference_mark_end(
480        self,
481        reference_mark: Element,
482        before: str | None = None,
483        after: str | None = None,
484        position: int = 0,
485    ) -> ReferenceMarkEnd:
486        """Insert/move a ReferenceMarkEnd for an existing reference mark. If
487        some end tag already exists, replace it. Reference tag is set at the
488        position defined by the regex (before or after).
489
490        If content/before or after (regex) returns a group of matching
491        positions, the position value is the index of matching place to use.
492
493        Arguments:
494
495            reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
496
497            before -- str regular expression or None
498
499            after -- str regular expression or None
500
501            position -- int
502        """
503        if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)):
504            raise TypeError("Not a ReferenceMark or ReferenceMarkStart")
505        name = reference_mark.name
506        if isinstance(reference_mark, ReferenceMark):
507            # change it to a range reference:
508            reference_mark.tag = ReferenceMarkStart._tag
509
510        existing_end_tag = self.get_reference_mark_end(name=name)
511        if existing_end_tag:
512            existing_end_tag.delete()
513
514        # create the end tag
515        end_tag = ReferenceMarkEnd(name)
516
517        # Insert
518        self._insert(
519            end_tag, before=before, after=after, position=position, main_text=True
520        )
521        return end_tag
522
523    def insert_variable(self, variable_element: Element, after: str | None) -> None:
524        self._insert(variable_element, after=after, main_text=True)
525
526    @_by_regex_offset
527    def set_span(
528        self,
529        match: str,
530        tail: str,
531        style: str,
532        regex: str | None = None,
533        offset: int | None = None,
534        length: int = 0,
535    ) -> Span:
536        """
537        set_span(style, regex=None, offset=None, length=0)
538        Apply the given style to text content matching the regex OR the
539        positional arguments offset and length.
540
541        (match, tail: provided by regex decorator)
542
543        Arguments:
544
545            style -- str
546
547            regex -- str regular expression
548
549            offset -- int
550
551            length -- int
552        """
553        span = Span(match, style=style)
554        span.tail = tail
555        return span
556
557    def remove_spans(self, keep_heading: bool = True) -> Element | list:
558        """Send back a copy of the element, without span styles.
559        If keep_heading is True (default), the first level heading style is left
560        unchanged.
561        """
562        strip = (Span._tag,)
563        if keep_heading:
564            protect = ("text:h",)
565        else:
566            protect = None
567        return self.strip_tags(strip=strip, protect=protect)
568
569    def remove_span(self, spans: Element | list[Element]) -> Element | list:
570        """Send back a copy of the element, the spans (not a clone) removed.
571
572        Arguments:
573
574            spans -- Element or list of Element
575        """
576        return self.strip_elements(spans)
577
578    @_by_regex_offset
579    def set_link(
580        self,
581        match: str,
582        tail: str,
583        url: str,
584        regex: str | None = None,
585        offset: int | None = None,
586        length: int = 0,
587    ) -> Element:
588        """
589        set_link(url, regex=None, offset=None, length=0)
590        Make a link to the provided url from text content matching the regex
591        OR the positional arguments offset and length.
592
593        (match, tail: provided by regex decorator)
594
595        Arguments:
596
597            url -- str
598
599            regex -- str regular expression
600
601            offset -- int
602
603            length -- int
604        """
605        link = Link(url, text=match)
606        link.tail = tail
607        return link
608
609    def remove_links(self) -> Element | list:
610        """Send back a copy of the element, without links tags."""
611        strip = (Link._tag,)
612        return self.strip_tags(strip=strip)
613
614    def remove_link(self, links: Link | list[Link]) -> Element | list:
615        """Send back a copy of the element (not a clone), with the sub links
616           removed.
617
618        Arguments:
619
620            links -- Link or list of Link
621        """
622        return self.strip_elements(links)
623
624    def insert_reference(
625        self,
626        name: str,
627        ref_format: str = "",
628        before: str | None = None,
629        after: str | Element | None = None,
630        position: int = 0,
631        display: str | None = None,
632    ) -> None:
633        """Create and insert a reference to a content marked by a reference
634        mark. The Reference element ("text:reference-ref") represents a
635        field that references a "text:reference-mark-start" or
636        "text:reference-mark" element. Its "text:reference-format" attribute
637        specifies what is displayed from the referenced element. Default is
638        'page'. Actual content is not automatically updated except for the 'text'
639        format.
640
641        name is mandatory and should represent an existing reference mark of the
642        document.
643
644        ref_format is the argument for format reference (default is 'page').
645
646        The reference is inserted the position defined by the regex (before /
647        after), or by positionnal argument (position). If 'display' is provided,
648        it will be used as the text value for the reference.
649
650        If after is an ODF Element, the reference is inserted as first child of
651        this element.
652
653        Arguments:
654
655            name -- str
656
657            ref_format -- one of : 'chapter', 'direction', 'page', 'text',
658                                    'caption', 'category-and-value', 'value',
659                                    'number', 'number-all-superior',
660                                    'number-no-superior'
661
662            before -- str regular expression or None
663
664            after -- str regular expression or odf element or None
665
666            position -- int
667
668            display -- str or None
669        """
670        reference = Reference(name, ref_format)
671        if display is None and ref_format == "text":
672            # get reference content
673            body = self.document_body
674            if not body:
675                body = self.root
676            mark = body.get_reference_mark(name=name)
677            if mark:
678                display = mark.referenced_text  # type: ignore
679        if not display:
680            display = " "
681        reference.text = display
682        if isinstance(after, Element):
683            after.insert(reference, FIRST_CHILD)
684        else:
685            self._insert(
686                reference, before=before, after=after, position=position, main_text=True
687            )
688
689    def set_bookmark(
690        self,
691        name: str,
692        before: str | None = None,
693        after: str | None = None,
694        position: int | tuple = 0,
695        role: str | None = None,
696        content: str | None = None,
697    ) -> Element | tuple[Element, Element]:
698        """Insert a bookmark before or after the characters in the text which
699        match the regex before/after. When the regex matches more of one part
700        of the text, position can be set to choose which part must be used.
701        If before and after are None, we use only position that is the number
702        of characters.
703
704        So, by default, this function inserts a bookmark before the first
705        character of the content. Role can be None, "start" or "end", we
706        insert respectively a position bookmark a bookmark-start or a
707        bookmark-end.
708
709        If content is not None these 2 calls are equivalent:
710
711          paragraph.set_bookmark("bookmark", content="xyz")
712
713        and:
714
715          paragraph.set_bookmark("bookmark", before="xyz", role="start")
716          paragraph.set_bookmark("bookmark", after="xyz", role="end")
717
718
719        If position is a 2-tuple, these 2 calls are equivalent:
720
721          paragraph.set_bookmark("bookmark", position=(10, 20))
722
723        and:
724
725          paragraph.set_bookmark("bookmark", position=10, role="start")
726          paragraph.set_bookmark("bookmark", position=20, role="end")
727
728
729        Arguments:
730
731            name -- str
732
733            before -- str regex
734
735            after -- str regex
736
737            position -- int or (int, int)
738
739            role -- None, "start" or "end"
740
741            content -- str regex
742        """
743        # With "content" => automatically insert a "start" and an "end"
744        # bookmark
745        if (
746            before is None
747            and after is None
748            and role is None
749            and content is not None
750            and isinstance(position, int)
751        ):
752            # Start
753            start = BookmarkStart(name)
754            self._insert(start, before=content, position=position, main_text=True)
755            # End
756            end = BookmarkEnd(name)
757            self._insert(end, after=content, position=position, main_text=True)
758            return start, end
759
760        # With "(int, int)" =>  automatically insert a "start" and an "end"
761        # bookmark
762        if (
763            before is None
764            and after is None
765            and role is None
766            and content is None
767            and isinstance(position, tuple)
768        ):
769            # Start
770            start = BookmarkStart(name)
771            self._insert(start, position=position[0], main_text=True)
772            # End
773            end = BookmarkEnd(name)
774            self._insert(end, position=position[1], main_text=True)
775            return start, end
776
777        # Without "content" nor "position"
778        if content is not None or not isinstance(position, int):
779            raise ValueError("bad arguments")
780
781        # Role
782        if role is None:
783            bookmark: Element = Bookmark(name)
784        elif role == "start":
785            bookmark = BookmarkStart(name)
786        elif role == "end":
787            bookmark = BookmarkEnd(name)
788        else:
789            raise ValueError("bad arguments")
790
791        # Insert
792        self._insert(
793            bookmark, before=before, after=after, position=position, main_text=True
794        )
795
796        return bookmark

Specialised element for paragraphs "text:p". The "text:p" element represents a paragraph, which is the basic unit of text in an OpenDocument file.

Paragraph( text_or_element: str | Element | None = None, style: str | None = None, **kwargs: Any)
155    def __init__(
156        self,
157        text_or_element: str | Element | None = None,
158        style: str | None = None,
159        **kwargs: Any,
160    ):
161        """Create a paragraph element of the given style containing the optional
162        given text.
163
164        Arguments:
165
166            text -- str or Element
167
168            style -- str
169        """
170        super().__init__(**kwargs)
171        if self._do_init:
172            if isinstance(text_or_element, Element):
173                self.append(text_or_element)
174            else:
175                self.text = text_or_element  # type:ignore
176            if style is not None:
177                self.style = style

Create a paragraph element of the given style containing the optional given text.

Arguments:

text -- str or Element

style -- str
def insert_note( self, note_element: Note | None = None, after: str | Element | None = None, note_class: str = 'footnote', note_id: str | None = None, citation: str | None = None, body: str | None = None) -> None:
179    def insert_note(
180        self,
181        note_element: Note | None = None,
182        after: str | Element | None = None,
183        note_class: str = "footnote",
184        note_id: str | None = None,
185        citation: str | None = None,
186        body: str | None = None,
187    ) -> None:
188        if note_element is None:
189            note_element = Note(
190                note_class=note_class, note_id=note_id, citation=citation, body=body
191            )
192        else:
193            # XXX clone or modify the argument?
194            if note_class:
195                note_element.note_class = note_class
196            if note_id:
197                note_element.note_id = note_id
198            if citation:
199                note_element.citation = citation
200            if body:
201                note_element.note_body = body
202        note_element.check_validity()
203        if isinstance(after, str):
204            self._insert(note_element, after=after, main_text=True)
205        elif isinstance(after, Element):
206            after.insert(note_element, FIRST_CHILD)
207        else:
208            self.insert(note_element, FIRST_CHILD)
def insert_annotation( self, annotation_element: Annotation | None = None, before: str | None = None, after: str | Element | None = None, position: int | tuple = 0, content: str | Element | None = None, body: str | None = None, creator: str | None = None, date: datetime.datetime | None = None) -> Annotation:
210    def insert_annotation(  # noqa: C901
211        self,
212        annotation_element: Annotation | None = None,
213        before: str | None = None,
214        after: str | Element | None = None,
215        position: int | tuple = 0,
216        content: str | Element | None = None,
217        body: str | None = None,
218        creator: str | None = None,
219        date: datetime | None = None,
220    ) -> Annotation:
221        """Insert an annotation, at the position defined by the regex (before,
222        after, content) or by positionnal argument (position). If content is
223        provided, the annotation covers the full content regex. Else, the
224        annotation is positionned either 'before' or 'after' provided regex.
225
226        If content is an odf element (ie: paragraph, span, ...), the full inner
227        content is covered by the annotation (of the position just after if
228        content is a single empty tag).
229
230        If content/before or after exists (regex) and return a group of matching
231        positions, the position value is the index of matching place to use.
232
233        annotation_element can contain a previously created annotation, else
234        the annotation is created from the body, creator and optional date
235        (current date by default).
236
237        Arguments:
238
239            annotation_element -- Annotation or None
240
241            before -- str regular expression or None
242
243            after -- str regular expression or Element or None
244
245            content -- str regular expression or None, or Element
246
247            position -- int or tuple of int
248
249            body -- str or Element
250
251            creator -- str
252
253            date -- datetime
254        """
255
256        if annotation_element is None:
257            annotation_element = Annotation(
258                text_or_element=body, creator=creator, date=date, parent=self
259            )
260        else:
261            # XXX clone or modify the argument?
262            if body:
263                annotation_element.note_body = body
264            if creator:
265                annotation_element.dc_creator = creator
266            if date:
267                annotation_element.dc_date = date
268        annotation_element.check_validity()
269
270        # special case: content is an odf element (ie: a paragraph)
271        if isinstance(content, Element):
272            if content.is_empty():
273                content.insert(annotation_element, xmlposition=NEXT_SIBLING)
274                return annotation_element
275            content.insert(annotation_element, start=True)
276            annotation_end = AnnotationEnd(annotation_element)
277            content.append(annotation_end)
278            return annotation_element
279
280        # special case
281        if isinstance(after, Element):
282            after.insert(annotation_element, FIRST_CHILD)
283            return annotation_element
284
285        # With "content" => automatically insert a "start" and an "end"
286        # bookmark
287        if (
288            before is None
289            and after is None
290            and content is not None
291            and isinstance(position, int)
292        ):
293            # Start tag
294            self._insert(
295                annotation_element, before=content, position=position, main_text=True
296            )
297            # End tag
298            annotation_end = AnnotationEnd(annotation_element)
299            self._insert(
300                annotation_end, after=content, position=position, main_text=True
301            )
302            return annotation_element
303
304        # With "(int, int)" =>  automatically insert a "start" and an "end"
305        # bookmark
306        if (
307            before is None
308            and after is None
309            and content is None
310            and isinstance(position, tuple)
311        ):
312            # Start
313            self._insert(annotation_element, position=position[0], main_text=True)
314            # End
315            annotation_end = AnnotationEnd(annotation_element)
316            self._insert(annotation_end, position=position[1], main_text=True)
317            return annotation_element
318
319        # Without "content" nor "position"
320        if content is not None or not isinstance(position, int):
321            raise ValueError("Bad arguments")
322
323        # Insert
324        self._insert(
325            annotation_element,
326            before=before,
327            after=after,
328            position=position,
329            main_text=True,
330        )
331        return annotation_element

Insert an annotation, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full content regex. Else, the annotation is positionned either 'before' or 'after' provided regex.

If content is an odf element (ie: paragraph, span, ...), the full inner content is covered by the annotation (of the position just after if content is a single empty tag).

If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.

annotation_element can contain a previously created annotation, else the annotation is created from the body, creator and optional date (current date by default).

Arguments:

annotation_element -- Annotation or None

before -- str regular expression or None

after -- str regular expression or Element or None

content -- str regular expression or None, or Element

position -- int or tuple of int

body -- str or Element

creator -- str

date -- datetime
def insert_annotation_end( self, annotation_element: Annotation, before: str | None = None, after: str | None = None, position: int = 0) -> AnnotationEnd:
333    def insert_annotation_end(
334        self,
335        annotation_element: Annotation,
336        before: str | None = None,
337        after: str | None = None,
338        position: int = 0,
339    ) -> AnnotationEnd:
340        """Insert an annotation end tag for an existing annotation. If some end
341        tag already exists, replace it. Annotation end tag is set at the
342        position defined by the regex (before or after).
343
344        If content/before or after (regex) returns a group of matching
345        positions, the position value is the index of matching place to use.
346
347        Arguments:
348
349            annotation_element -- Annotation (mandatory)
350
351            before -- str regular expression or None
352
353            after -- str regular expression or None
354
355            position -- int
356        """
357
358        if annotation_element is None:
359            raise ValueError
360        if not isinstance(annotation_element, Annotation):
361            raise TypeError("Not a <office:annotation> Annotation")
362
363        # remove existing end tag
364        name = annotation_element.name
365        existing_end_tag = self.get_annotation_end(name=name)
366        if existing_end_tag:
367            existing_end_tag.delete()
368
369        # create the end tag
370        end_tag = AnnotationEnd(annotation_element)
371
372        # Insert
373        self._insert(
374            end_tag, before=before, after=after, position=position, main_text=True
375        )
376        return end_tag

Insert an annotation end tag for an existing annotation. If some end tag already exists, replace it. Annotation end tag is set at the position defined by the regex (before or after).

If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.

Arguments:

annotation_element -- Annotation (mandatory)

before -- str regular expression or None

after -- str regular expression or None

position -- int
def set_reference_mark( self, name: str, before: str | None = None, after: str | None = None, position: int = 0, content: str | Element | None = None) -> Element:
378    def set_reference_mark(
379        self,
380        name: str,
381        before: str | None = None,
382        after: str | None = None,
383        position: int = 0,
384        content: str | Element | None = None,
385    ) -> Element:
386        """Insert a reference mark, at the position defined by the regex
387        (before, after, content) or by positionnal argument (position). If
388        content is provided, the annotation covers the full range content regex
389        (instances of ReferenceMarkStart and ReferenceMarkEnd are
390        created). Else, an instance of ReferenceMark is positionned either
391        'before' or 'after' provided regex.
392
393        If content is an ODF Element (ie: Paragraph, Span, ...), the full inner
394        content is referenced (of the position just after if content is a single
395        empty tag).
396
397        If content/before or after exists (regex) and return a group of matching
398        positions, the position value is the index of matching place to use.
399
400        Name is mandatory and shall be unique in the document for the preference
401        mark range.
402
403        Arguments:
404
405            name -- str
406
407            before -- str regular expression or None
408
409            after -- str regular expression or None,
410
411            content -- str regular expression or None, or Element
412
413            position -- int or tuple of int
414
415        Return: the created ReferenceMark or ReferenceMarkStart
416        """
417        # special case: content is an odf element (ie: a paragraph)
418        if isinstance(content, Element):
419            if content.is_empty():
420                reference = ReferenceMark(name)
421                content.insert(reference, xmlposition=NEXT_SIBLING)
422                return reference
423            reference_start = ReferenceMarkStart(name)
424            content.insert(reference_start, start=True)
425            reference_end = ReferenceMarkEnd(name)
426            content.append(reference_end)
427            return reference_start
428
429        # With "content" => automatically insert a "start" and an "end"
430        # reference
431        if (
432            before is None
433            and after is None
434            and content is not None
435            and isinstance(position, int)
436        ):
437            # Start tag
438            reference_start = ReferenceMarkStart(name)
439            self._insert(
440                reference_start, before=content, position=position, main_text=True
441            )
442            # End tag
443            reference_end = ReferenceMarkEnd(name)
444            self._insert(
445                reference_end, after=content, position=position, main_text=True
446            )
447            return reference_start
448
449        # With "(int, int)" =>  automatically insert a "start" and an "end"
450        if (
451            before is None
452            and after is None
453            and content is None
454            and isinstance(position, tuple)
455        ):
456            # Start
457            reference_start = ReferenceMarkStart(name)
458            self._insert(reference_start, position=position[0], main_text=True)
459            # End
460            reference_end = ReferenceMarkEnd(name)
461            self._insert(reference_end, position=position[1], main_text=True)
462            return reference_start
463
464        # Without "content" nor "position"
465        if content is not None or not isinstance(position, int):
466            raise ValueError("bad arguments")
467
468        # Insert a positional reference mark
469        reference = ReferenceMark(name)
470        self._insert(
471            reference,
472            before=before,
473            after=after,
474            position=position,
475            main_text=True,
476        )
477        return reference

Insert a reference mark, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full range content regex (instances of ReferenceMarkStart and ReferenceMarkEnd are created). Else, an instance of ReferenceMark is positionned either 'before' or 'after' provided regex.

If content is an ODF Element (ie: Paragraph, Span, ...), the full inner content is referenced (of the position just after if content is a single empty tag).

If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.

Name is mandatory and shall be unique in the document for the preference mark range.

Arguments:

name -- str

before -- str regular expression or None

after -- str regular expression or None,

content -- str regular expression or None, or Element

position -- int or tuple of int

Return: the created ReferenceMark or ReferenceMarkStart

def set_reference_mark_end( self, reference_mark: Element, before: str | None = None, after: str | None = None, position: int = 0) -> ReferenceMarkEnd:
479    def set_reference_mark_end(
480        self,
481        reference_mark: Element,
482        before: str | None = None,
483        after: str | None = None,
484        position: int = 0,
485    ) -> ReferenceMarkEnd:
486        """Insert/move a ReferenceMarkEnd for an existing reference mark. If
487        some end tag already exists, replace it. Reference tag is set at the
488        position defined by the regex (before or after).
489
490        If content/before or after (regex) returns a group of matching
491        positions, the position value is the index of matching place to use.
492
493        Arguments:
494
495            reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
496
497            before -- str regular expression or None
498
499            after -- str regular expression or None
500
501            position -- int
502        """
503        if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)):
504            raise TypeError("Not a ReferenceMark or ReferenceMarkStart")
505        name = reference_mark.name
506        if isinstance(reference_mark, ReferenceMark):
507            # change it to a range reference:
508            reference_mark.tag = ReferenceMarkStart._tag
509
510        existing_end_tag = self.get_reference_mark_end(name=name)
511        if existing_end_tag:
512            existing_end_tag.delete()
513
514        # create the end tag
515        end_tag = ReferenceMarkEnd(name)
516
517        # Insert
518        self._insert(
519            end_tag, before=before, after=after, position=position, main_text=True
520        )
521        return end_tag

Insert/move a ReferenceMarkEnd for an existing reference mark. If some end tag already exists, replace it. Reference tag is set at the position defined by the regex (before or after).

If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.

Arguments:

reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)

before -- str regular expression or None

after -- str regular expression or None

position -- int
def insert_variable(self, variable_element: Element, after: str | None) -> None:
523    def insert_variable(self, variable_element: Element, after: str | None) -> None:
524        self._insert(variable_element, after=after, main_text=True)
def set_span( self, match: str, tail: str, style: str, regex: str | None = None, offset: int | None = None, length: int = 0) -> Span:
526    @_by_regex_offset
527    def set_span(
528        self,
529        match: str,
530        tail: str,
531        style: str,
532        regex: str | None = None,
533        offset: int | None = None,
534        length: int = 0,
535    ) -> Span:
536        """
537        set_span(style, regex=None, offset=None, length=0)
538        Apply the given style to text content matching the regex OR the
539        positional arguments offset and length.
540
541        (match, tail: provided by regex decorator)
542
543        Arguments:
544
545            style -- str
546
547            regex -- str regular expression
548
549            offset -- int
550
551            length -- int
552        """
553        span = Span(match, style=style)
554        span.tail = tail
555        return span

set_span(style, regex=None, offset=None, length=0) Apply the given style to text content matching the regex OR the positional arguments offset and length.

(match, tail: provided by regex decorator)

Arguments:

style -- str

regex -- str regular expression

offset -- int

length -- int
def remove_spans(self, keep_heading: bool = True) -> Element | list:
557    def remove_spans(self, keep_heading: bool = True) -> Element | list:
558        """Send back a copy of the element, without span styles.
559        If keep_heading is True (default), the first level heading style is left
560        unchanged.
561        """
562        strip = (Span._tag,)
563        if keep_heading:
564            protect = ("text:h",)
565        else:
566            protect = None
567        return self.strip_tags(strip=strip, protect=protect)

Send back a copy of the element, without span styles. If keep_heading is True (default), the first level heading style is left unchanged.

def remove_span( self, spans: Element | list[Element]) -> Element | list:
569    def remove_span(self, spans: Element | list[Element]) -> Element | list:
570        """Send back a copy of the element, the spans (not a clone) removed.
571
572        Arguments:
573
574            spans -- Element or list of Element
575        """
576        return self.strip_elements(spans)

Send back a copy of the element, the spans (not a clone) removed.

Arguments:

spans -- Element or list of Element
def insert_reference( self, name: str, ref_format: str = '', before: str | None = None, after: str | Element | None = None, position: int = 0, display: str | None = None) -> None:
624    def insert_reference(
625        self,
626        name: str,
627        ref_format: str = "",
628        before: str | None = None,
629        after: str | Element | None = None,
630        position: int = 0,
631        display: str | None = None,
632    ) -> None:
633        """Create and insert a reference to a content marked by a reference
634        mark. The Reference element ("text:reference-ref") represents a
635        field that references a "text:reference-mark-start" or
636        "text:reference-mark" element. Its "text:reference-format" attribute
637        specifies what is displayed from the referenced element. Default is
638        'page'. Actual content is not automatically updated except for the 'text'
639        format.
640
641        name is mandatory and should represent an existing reference mark of the
642        document.
643
644        ref_format is the argument for format reference (default is 'page').
645
646        The reference is inserted the position defined by the regex (before /
647        after), or by positionnal argument (position). If 'display' is provided,
648        it will be used as the text value for the reference.
649
650        If after is an ODF Element, the reference is inserted as first child of
651        this element.
652
653        Arguments:
654
655            name -- str
656
657            ref_format -- one of : 'chapter', 'direction', 'page', 'text',
658                                    'caption', 'category-and-value', 'value',
659                                    'number', 'number-all-superior',
660                                    'number-no-superior'
661
662            before -- str regular expression or None
663
664            after -- str regular expression or odf element or None
665
666            position -- int
667
668            display -- str or None
669        """
670        reference = Reference(name, ref_format)
671        if display is None and ref_format == "text":
672            # get reference content
673            body = self.document_body
674            if not body:
675                body = self.root
676            mark = body.get_reference_mark(name=name)
677            if mark:
678                display = mark.referenced_text  # type: ignore
679        if not display:
680            display = " "
681        reference.text = display
682        if isinstance(after, Element):
683            after.insert(reference, FIRST_CHILD)
684        else:
685            self._insert(
686                reference, before=before, after=after, position=position, main_text=True
687            )

Create and insert a reference to a content marked by a reference mark. The Reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its "text:reference-format" attribute specifies what is displayed from the referenced element. Default is 'page'. Actual content is not automatically updated except for the 'text' format.

name is mandatory and should represent an existing reference mark of the document.

ref_format is the argument for format reference (default is 'page').

The reference is inserted the position defined by the regex (before / after), or by positionnal argument (position). If 'display' is provided, it will be used as the text value for the reference.

If after is an ODF Element, the reference is inserted as first child of this element.

Arguments:

name -- str

ref_format -- one of : 'chapter', 'direction', 'page', 'text',
                        'caption', 'category-and-value', 'value',
                        'number', 'number-all-superior',
                        'number-no-superior'

before -- str regular expression or None

after -- str regular expression or odf element or None

position -- int

display -- str or None
def set_bookmark( self, name: str, before: str | None = None, after: str | None = None, position: int | tuple = 0, role: str | None = None, content: str | None = None) -> Element | tuple[Element, Element]:
689    def set_bookmark(
690        self,
691        name: str,
692        before: str | None = None,
693        after: str | None = None,
694        position: int | tuple = 0,
695        role: str | None = None,
696        content: str | None = None,
697    ) -> Element | tuple[Element, Element]:
698        """Insert a bookmark before or after the characters in the text which
699        match the regex before/after. When the regex matches more of one part
700        of the text, position can be set to choose which part must be used.
701        If before and after are None, we use only position that is the number
702        of characters.
703
704        So, by default, this function inserts a bookmark before the first
705        character of the content. Role can be None, "start" or "end", we
706        insert respectively a position bookmark a bookmark-start or a
707        bookmark-end.
708
709        If content is not None these 2 calls are equivalent:
710
711          paragraph.set_bookmark("bookmark", content="xyz")
712
713        and:
714
715          paragraph.set_bookmark("bookmark", before="xyz", role="start")
716          paragraph.set_bookmark("bookmark", after="xyz", role="end")
717
718
719        If position is a 2-tuple, these 2 calls are equivalent:
720
721          paragraph.set_bookmark("bookmark", position=(10, 20))
722
723        and:
724
725          paragraph.set_bookmark("bookmark", position=10, role="start")
726          paragraph.set_bookmark("bookmark", position=20, role="end")
727
728
729        Arguments:
730
731            name -- str
732
733            before -- str regex
734
735            after -- str regex
736
737            position -- int or (int, int)
738
739            role -- None, "start" or "end"
740
741            content -- str regex
742        """
743        # With "content" => automatically insert a "start" and an "end"
744        # bookmark
745        if (
746            before is None
747            and after is None
748            and role is None
749            and content is not None
750            and isinstance(position, int)
751        ):
752            # Start
753            start = BookmarkStart(name)
754            self._insert(start, before=content, position=position, main_text=True)
755            # End
756            end = BookmarkEnd(name)
757            self._insert(end, after=content, position=position, main_text=True)
758            return start, end
759
760        # With "(int, int)" =>  automatically insert a "start" and an "end"
761        # bookmark
762        if (
763            before is None
764            and after is None
765            and role is None
766            and content is None
767            and isinstance(position, tuple)
768        ):
769            # Start
770            start = BookmarkStart(name)
771            self._insert(start, position=position[0], main_text=True)
772            # End
773            end = BookmarkEnd(name)
774            self._insert(end, position=position[1], main_text=True)
775            return start, end
776
777        # Without "content" nor "position"
778        if content is not None or not isinstance(position, int):
779            raise ValueError("bad arguments")
780
781        # Role
782        if role is None:
783            bookmark: Element = Bookmark(name)
784        elif role == "start":
785            bookmark = BookmarkStart(name)
786        elif role == "end":
787            bookmark = BookmarkEnd(name)
788        else:
789            raise ValueError("bad arguments")
790
791        # Insert
792        self._insert(
793            bookmark, before=before, after=after, position=position, main_text=True
794        )
795
796        return bookmark

Insert a bookmark before or after the characters in the text which match the regex before/after. When the regex matches more of one part of the text, position can be set to choose which part must be used. If before and after are None, we use only position that is the number of characters.

So, by default, this function inserts a bookmark before the first character of the content. Role can be None, "start" or "end", we insert respectively a position bookmark a bookmark-start or a bookmark-end.

If content is not None these 2 calls are equivalent:

paragraph.set_bookmark("bookmark", content="xyz")

and:

paragraph.set_bookmark("bookmark", before="xyz", role="start") paragraph.set_bookmark("bookmark", after="xyz", role="end")

If position is a 2-tuple, these 2 calls are equivalent:

paragraph.set_bookmark("bookmark", position=(10, 20))

and:

paragraph.set_bookmark("bookmark", position=10, role="start") paragraph.set_bookmark("bookmark", position=20, role="end")

Arguments:

name -- str

before -- str regex

after -- str regex

position -- int or (int, int)

role -- None, "start" or "end"

content -- str regex
Inherited Members
odfdo.paragraph_base.ParagraphBase
get_formatted_text
append_plain_text
style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class RectangleShape(odfdo.shapes.ShapeBase):
146class RectangleShape(ShapeBase):
147    """Create a rectangle shape.
148
149    Arguments:
150
151        style -- str
152
153        text_style -- str
154
155        draw_id -- str
156
157        layer -- str
158
159        position -- (str, str)
160
161        size -- (str, str)
162
163    """
164
165    _tag = "draw:rect"
166    _properties: tuple[PropDef, ...] = ()
167
168    def __init__(
169        self,
170        style: str | None = None,
171        text_style: str | None = None,
172        draw_id: str | None = None,
173        layer: str | None = None,
174        position: tuple | None = None,
175        size: tuple | None = None,
176        **kwargs: Any,
177    ) -> None:
178        kwargs.update(
179            {
180                "style": style,
181                "text_style": text_style,
182                "draw_id": draw_id,
183                "layer": layer,
184                "size": size,
185                "position": position,
186            }
187        )
188        super().__init__(**kwargs)

Create a rectangle shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

position -- (str, str)

size -- (str, str)
RectangleShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, position: tuple | None = None, size: tuple | None = None, **kwargs: Any)
168    def __init__(
169        self,
170        style: str | None = None,
171        text_style: str | None = None,
172        draw_id: str | None = None,
173        layer: str | None = None,
174        position: tuple | None = None,
175        size: tuple | None = None,
176        **kwargs: Any,
177    ) -> None:
178        kwargs.update(
179            {
180                "style": style,
181                "text_style": text_style,
182                "draw_id": draw_id,
183                "layer": layer,
184                "size": size,
185                "position": position,
186            }
187        )
188        super().__init__(**kwargs)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class Reference(odfdo.Element):
 59class Reference(Element):
 60    """A reference to a content marked by a reference mark.
 61    The odf_reference element ("text:reference-ref") represents a field that
 62    references a "text:reference-mark-start" or "text:reference-mark" element.
 63    Its text:reference-format attribute specifies what is displayed from the
 64    referenced element. Default is 'page'
 65    Actual content is not updated except for the 'text' format by the
 66    update() method.
 67
 68
 69    Creation of references can be tricky, consider using this method:
 70        odfdo.paragraph.insert_reference()
 71
 72    Values for text:reference-format :
 73        The defined values for the text:reference-format attribute supported by
 74        all reference fields are:
 75          - 'chapter': displays the number of the chapter in which the
 76            referenced item appears.
 77          - 'direction': displays whether the referenced item is above or
 78            below the reference field.
 79          - 'page': displays the number of the page on which the referenced
 80            item appears.
 81          - 'text': displays the text of the referenced item.
 82        Additional defined values for the text:reference-format attribute
 83        supported by references to sequence fields are:
 84          - 'caption': displays the caption in which the sequence is used.
 85          - 'category-and-value': displays the name and value of the sequence.
 86          - 'value': displays the value of the sequence.
 87
 88        References to bookmarks and other references support additional values,
 89        which display the list label of the referenced item. If the referenced
 90        item is contained in a list or a numbered paragraph, the list label is
 91        the formatted number of the paragraph which contains the referenced
 92        item. If the referenced item is not contained in a list or numbered
 93        paragraph, the list label is empty, and the referenced field therefore
 94        displays nothing. If the referenced bookmark or reference contains more
 95        than one paragraph, the list label of the paragraph at which the
 96        bookmark or reference starts is taken.
 97
 98        Additional defined values for the text:reference-format attribute
 99        supported by all references to bookmark's or other reference fields
100        are:
101          - 'number': displays the list label of the referenced item. [...]
102          - 'number-all-superior': displays the list label of the referenced
103            item and adds the contents of all list labels of superior levels
104            in front of it. [...]
105          - 'number-no-superior': displays the contents of the list label of
106            the referenced item.
107    """
108
109    _tag = "text:reference-ref"
110    _properties = (PropDef("name", "text:ref-name"),)
111    format_allowed = (
112        "chapter",
113        "direction",
114        "page",
115        "text",
116        "caption",
117        "category-and-value",
118        "value",
119        "number",
120        "number-all-superior",
121        "number-no-superior",
122    )
123
124    def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None:
125        """Create a reference to a content marked by a reference mark. An
126        actual reference mark with the provided name should exist.
127
128        Consider using: odfdo.paragraph.insert_reference()
129
130        The text:ref-name attribute identifies a "text:reference-mark" or
131        "text:referencemark-start" element by the value of that element's
132        text:name attribute.
133        If ref_format is 'text', the current text content of the reference_mark
134        is retrieved.
135
136        Arguments:
137
138            name -- str : name of the reference mark
139
140            ref_format -- str : format of the field. Default is 'page', allowed
141                            values are 'chapter', 'direction', 'page', 'text',
142                            'caption', 'category-and-value', 'value', 'number',
143                            'number-all-superior', 'number-no-superior'.
144        """
145        super().__init__(**kwargs)
146        if self._do_init:
147            self.name = name
148            self.ref_format = ref_format
149
150    @property
151    def ref_format(self) -> str | None:
152        reference = self.get_attribute("text:reference-format")
153        if isinstance(reference, str):
154            return reference
155        return None
156
157    @ref_format.setter
158    def ref_format(self, ref_format: str) -> None:
159        """Set the text:reference-format attribute.
160
161        Arguments:
162
163            ref_format -- str
164        """
165        if not ref_format or ref_format not in self.format_allowed:
166            ref_format = "page"
167        self.set_attribute("text:reference-format", ref_format)
168
169    def update(self) -> None:
170        """Update the content of the reference text field. Currently only
171        'text' format is implemented. Other values, for example the 'page' text
172        field, may need to be refreshed through a visual ODF parser.
173        """
174        ref_format = self.ref_format
175        if ref_format != "text":
176            # only 'text' is implemented
177            return None
178        body = self.document_body
179        if not body:
180            body = self.root
181        name = self.name
182        reference = body.get_reference_mark(name=name)
183        if not reference:
184            return None
185        # we know it is a ReferenceMarkStart:
186        self.text = reference.referenced_text()  # type: ignore

A reference to a content marked by a reference mark. The odf_reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its text:reference-format attribute specifies what is displayed from the referenced element. Default is 'page' Actual content is not updated except for the 'text' format by the update() method.

Creation of references can be tricky, consider using this method: odfdo.paragraph.insert_reference()

Values for text:reference-format : The defined values for the text:reference-format attribute supported by all reference fields are: - 'chapter': displays the number of the chapter in which the referenced item appears. - 'direction': displays whether the referenced item is above or below the reference field. - 'page': displays the number of the page on which the referenced item appears. - 'text': displays the text of the referenced item. Additional defined values for the text:reference-format attribute supported by references to sequence fields are: - 'caption': displays the caption in which the sequence is used. - 'category-and-value': displays the name and value of the sequence. - 'value': displays the value of the sequence.

References to bookmarks and other references support additional values,
which display the list label of the referenced item. If the referenced
item is contained in a list or a numbered paragraph, the list label is
the formatted number of the paragraph which contains the referenced
item. If the referenced item is not contained in a list or numbered
paragraph, the list label is empty, and the referenced field therefore
displays nothing. If the referenced bookmark or reference contains more
than one paragraph, the list label of the paragraph at which the
bookmark or reference starts is taken.

Additional defined values for the text:reference-format attribute
supported by all references to bookmark's or other reference fields
are:
  - 'number': displays the list label of the referenced item. [...]
  - 'number-all-superior': displays the list label of the referenced
    item and adds the contents of all list labels of superior levels
    in front of it. [...]
  - 'number-no-superior': displays the contents of the list label of
    the referenced item.
Reference(name: str = '', ref_format: str = '', **kwargs: Any)
124    def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None:
125        """Create a reference to a content marked by a reference mark. An
126        actual reference mark with the provided name should exist.
127
128        Consider using: odfdo.paragraph.insert_reference()
129
130        The text:ref-name attribute identifies a "text:reference-mark" or
131        "text:referencemark-start" element by the value of that element's
132        text:name attribute.
133        If ref_format is 'text', the current text content of the reference_mark
134        is retrieved.
135
136        Arguments:
137
138            name -- str : name of the reference mark
139
140            ref_format -- str : format of the field. Default is 'page', allowed
141                            values are 'chapter', 'direction', 'page', 'text',
142                            'caption', 'category-and-value', 'value', 'number',
143                            'number-all-superior', 'number-no-superior'.
144        """
145        super().__init__(**kwargs)
146        if self._do_init:
147            self.name = name
148            self.ref_format = ref_format

Create a reference to a content marked by a reference mark. An actual reference mark with the provided name should exist.

Consider using: odfdo.paragraph.insert_reference()

The text:ref-name attribute identifies a "text:reference-mark" or "text:referencemark-start" element by the value of that element's text:name attribute. If ref_format is 'text', the current text content of the reference_mark is retrieved.

Arguments:

name -- str : name of the reference mark

ref_format -- str : format of the field. Default is 'page', allowed
                values are 'chapter', 'direction', 'page', 'text',
                'caption', 'category-and-value', 'value', 'number',
                'number-all-superior', 'number-no-superior'.
format_allowed = ('chapter', 'direction', 'page', 'text', 'caption', 'category-and-value', 'value', 'number', 'number-all-superior', 'number-no-superior')
ref_format: str | None
150    @property
151    def ref_format(self) -> str | None:
152        reference = self.get_attribute("text:reference-format")
153        if isinstance(reference, str):
154            return reference
155        return None

Set the text:reference-format attribute.

Arguments:

ref_format -- str
def update(self) -> None:
169    def update(self) -> None:
170        """Update the content of the reference text field. Currently only
171        'text' format is implemented. Other values, for example the 'page' text
172        field, may need to be refreshed through a visual ODF parser.
173        """
174        ref_format = self.ref_format
175        if ref_format != "text":
176            # only 'text' is implemented
177            return None
178        body = self.document_body
179        if not body:
180            body = self.root
181        name = self.name
182        reference = body.get_reference_mark(name=name)
183        if not reference:
184            return None
185        # we know it is a ReferenceMarkStart:
186        self.text = reference.referenced_text()  # type: ignore

Update the content of the reference text field. Currently only 'text' format is implemented. Other values, for example the 'page' text field, may need to be refreshed through a visual ODF parser.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMark(odfdo.Element):
192class ReferenceMark(Element):
193    """A point reference.
194    A point reference marks a position in text and is represented by a single
195    "text:reference-mark" element.
196    """
197
198    _tag = "text:reference-mark"
199    _properties = (PropDef("name", "text:name"),)
200
201    def __init__(self, name: str = "", **kwargs: Any) -> None:
202        """A point reference. A point reference marks a position in text and is
203        represented by a single "text:reference-mark" element.
204        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
205
206        Arguments:
207
208            name -- str
209        """
210        super().__init__(**kwargs)
211        if self._do_init:
212            self.name = name

A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element.

ReferenceMark(name: str = '', **kwargs: Any)
201    def __init__(self, name: str = "", **kwargs: Any) -> None:
202        """A point reference. A point reference marks a position in text and is
203        represented by a single "text:reference-mark" element.
204        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
205
206        Arguments:
207
208            name -- str
209        """
210        super().__init__(**kwargs)
211        if self._do_init:
212            self.name = name

A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element. Consider using the wrapper: odfdo.paragraph.set_reference_mark()

Arguments:

name -- str
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMarkEnd(odfdo.Element):
218class ReferenceMarkEnd(Element):
219    """The "text:reference-mark-end" element represents the end of a range
220    reference.
221    """
222
223    _tag = "text:reference-mark-end"
224    _properties = (PropDef("name", "text:name"),)
225
226    def __init__(self, name: str = "", **kwargs: Any) -> None:
227        """The "text:reference-mark-end" element represent the end of a range
228        reference.
229        Consider using the wrappers: odfdo.paragraph.set_reference_mark() and
230        odfdo.paragraph.set_reference_mark_end()
231
232        Arguments:
233
234            name -- str
235        """
236        super().__init__(**kwargs)
237        if self._do_init:
238            self.name = name
239
240    def referenced_text(self) -> str:
241        """Return the text between reference-mark-start and reference-mark-end."""
242        name = self.name
243        request = (
244            f"//text()"
245            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
246            f"and following::text:reference-mark-end[@text:name='{name}']]"
247        )
248        result = " ".join(str(x) for x in self.xpath(request))
249        return result

The "text:reference-mark-end" element represents the end of a range reference.

ReferenceMarkEnd(name: str = '', **kwargs: Any)
226    def __init__(self, name: str = "", **kwargs: Any) -> None:
227        """The "text:reference-mark-end" element represent the end of a range
228        reference.
229        Consider using the wrappers: odfdo.paragraph.set_reference_mark() and
230        odfdo.paragraph.set_reference_mark_end()
231
232        Arguments:
233
234            name -- str
235        """
236        super().__init__(**kwargs)
237        if self._do_init:
238            self.name = name

The "text:reference-mark-end" element represent the end of a range reference. Consider using the wrappers: odfdo.paragraph.set_reference_mark() and odfdo.paragraph.set_reference_mark_end()

Arguments:

name -- str
def referenced_text(self) -> str:
240    def referenced_text(self) -> str:
241        """Return the text between reference-mark-start and reference-mark-end."""
242        name = self.name
243        request = (
244            f"//text()"
245            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
246            f"and following::text:reference-mark-end[@text:name='{name}']]"
247        )
248        result = " ".join(str(x) for x in self.xpath(request))
249        return result

Return the text between reference-mark-start and reference-mark-end.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMarkStart(odfdo.Element):
255class ReferenceMarkStart(Element):
256    """The "text:reference-mark-start" element represents the start of a
257    range reference.
258    """
259
260    _tag = "text:reference-mark-start"
261    _properties = (PropDef("name", "text:name"),)
262
263    def __init__(self, name: str = "", **kwargs: Any) -> None:
264        """The "text:reference-mark-start" element represent the start of a range
265        reference.
266        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
267
268        Arguments:
269
270            name -- str
271        """
272        super().__init__(**kwargs)
273        if self._do_init:
274            self.name = name
275
276    def referenced_text(self) -> str:
277        """Return the text between reference-mark-start and reference-mark-end."""
278        name = self.name
279        request = (
280            f"//text()"
281            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
282            f"and following::text:reference-mark-end[@text:name='{name}']]"
283        )
284        result = " ".join(str(x) for x in self.xpath(request))
285        return result
286
287    def get_referenced(
288        self,
289        no_header: bool = False,
290        clean: bool = True,
291        as_xml: bool = False,
292        as_list: bool = False,
293    ) -> Element | list | str | None:
294        """Return the document content between the start and end tags of the
295        reference. The content returned by this method can spread over several
296        headers and paragraphs.
297        By default, the content is returned as an "office:text" odf element.
298
299
300        Arguments:
301
302            no_header -- boolean (default to False), translate existing headers
303                         tags "text:h" into paragraphs "text:p".
304
305            clean -- boolean (default to True), suppress unwanted tags. Striped
306                     tags are : 'text:change', 'text:change-start',
307                     'text:change-end', 'text:reference-mark',
308                     'text:reference-mark-start', 'text:reference-mark-end'.
309
310            as_xml -- boolean (default to False), format the returned content as
311                      a XML string (serialization).
312
313            as_list -- boolean (default to False), do not embed the returned
314                       content in a "office:text'" element, instead simply
315                       return a raw list of odf elements.
316        """
317        name = self.name
318        parent = self.parent
319        if parent is None:
320            raise ValueError("Reference need some upper document part")
321        body = self.document_body
322        if not body:
323            body = parent
324        end = body.get_reference_mark_end(name=name)
325        if end is None:
326            raise ValueError("No reference-end found")
327        start = self
328        return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)
329
330    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
331        """Delete the given element from the XML tree. If no element is given,
332        "self" is deleted. The XML library may allow to continue to use an
333        element now "orphan" as long as you have a reference to it.
334
335        For odf_reference_mark_start : delete the reference-end tag if exists.
336
337        Arguments:
338
339            child -- Element
340
341            keep_tail -- boolean (default to True), True for most usages.
342        """
343        if child is not None:  # act like normal delete
344            return super().delete(child, keep_tail)
345        name = self.name
346        parent = self.parent
347        if parent is None:
348            raise ValueError("Can't delete the root element")
349        body = self.document_body
350        if not body:
351            body = parent
352        end = body.get_reference_mark_end(name=name)
353        if end:
354            end.delete()
355        # act like normal delete
356        return super().delete()

The "text:reference-mark-start" element represents the start of a range reference.

ReferenceMarkStart(name: str = '', **kwargs: Any)
263    def __init__(self, name: str = "", **kwargs: Any) -> None:
264        """The "text:reference-mark-start" element represent the start of a range
265        reference.
266        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
267
268        Arguments:
269
270            name -- str
271        """
272        super().__init__(**kwargs)
273        if self._do_init:
274            self.name = name

The "text:reference-mark-start" element represent the start of a range reference. Consider using the wrapper: odfdo.paragraph.set_reference_mark()

Arguments:

name -- str
def referenced_text(self) -> str:
276    def referenced_text(self) -> str:
277        """Return the text between reference-mark-start and reference-mark-end."""
278        name = self.name
279        request = (
280            f"//text()"
281            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
282            f"and following::text:reference-mark-end[@text:name='{name}']]"
283        )
284        result = " ".join(str(x) for x in self.xpath(request))
285        return result

Return the text between reference-mark-start and reference-mark-end.

def get_referenced( self, no_header: bool = False, clean: bool = True, as_xml: bool = False, as_list: bool = False) -> Element | list | str | None:
287    def get_referenced(
288        self,
289        no_header: bool = False,
290        clean: bool = True,
291        as_xml: bool = False,
292        as_list: bool = False,
293    ) -> Element | list | str | None:
294        """Return the document content between the start and end tags of the
295        reference. The content returned by this method can spread over several
296        headers and paragraphs.
297        By default, the content is returned as an "office:text" odf element.
298
299
300        Arguments:
301
302            no_header -- boolean (default to False), translate existing headers
303                         tags "text:h" into paragraphs "text:p".
304
305            clean -- boolean (default to True), suppress unwanted tags. Striped
306                     tags are : 'text:change', 'text:change-start',
307                     'text:change-end', 'text:reference-mark',
308                     'text:reference-mark-start', 'text:reference-mark-end'.
309
310            as_xml -- boolean (default to False), format the returned content as
311                      a XML string (serialization).
312
313            as_list -- boolean (default to False), do not embed the returned
314                       content in a "office:text'" element, instead simply
315                       return a raw list of odf elements.
316        """
317        name = self.name
318        parent = self.parent
319        if parent is None:
320            raise ValueError("Reference need some upper document part")
321        body = self.document_body
322        if not body:
323            body = parent
324        end = body.get_reference_mark_end(name=name)
325        if end is None:
326            raise ValueError("No reference-end found")
327        start = self
328        return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)

Return the document content between the start and end tags of the reference. The content returned by this method can spread over several headers and paragraphs. By default, the content is returned as an "office:text" odf element.

Arguments:

no_header -- boolean (default to False), translate existing headers
             tags "text:h" into paragraphs "text:p".

clean -- boolean (default to True), suppress unwanted tags. Striped
         tags are : 'text:change', 'text:change-start',
         'text:change-end', 'text:reference-mark',
         'text:reference-mark-start', 'text:reference-mark-end'.

as_xml -- boolean (default to False), format the returned content as
          a XML string (serialization).

as_list -- boolean (default to False), do not embed the returned
           content in a "office:text'" element, instead simply
           return a raw list of odf elements.
def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
330    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
331        """Delete the given element from the XML tree. If no element is given,
332        "self" is deleted. The XML library may allow to continue to use an
333        element now "orphan" as long as you have a reference to it.
334
335        For odf_reference_mark_start : delete the reference-end tag if exists.
336
337        Arguments:
338
339            child -- Element
340
341            keep_tail -- boolean (default to True), True for most usages.
342        """
343        if child is not None:  # act like normal delete
344            return super().delete(child, keep_tail)
345        name = self.name
346        parent = self.parent
347        if parent is None:
348            raise ValueError("Can't delete the root element")
349        body = self.document_body
350        if not body:
351            body = parent
352        end = body.get_reference_mark_end(name=name)
353        if end:
354            end.delete()
355        # act like normal delete
356        return super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For odf_reference_mark_start : delete the reference-end tag if exists.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Row(odfdo.Element):
 52class Row(Element):
 53    """ODF table row "table:table-row" """
 54
 55    _tag = "table:table-row"
 56    _caching = True
 57    _append = Element.append
 58
 59    def __init__(
 60        self,
 61        width: int | None = None,
 62        repeated: int | None = None,
 63        style: str | None = None,
 64        **kwargs: Any,
 65    ) -> None:
 66        """create a Row, optionally filled with "width" number of cells.
 67
 68        Rows contain cells, their number determine the number of columns.
 69
 70        You don't generally have to create rows by hand, use the Table API.
 71
 72        Arguments:
 73
 74            width -- int
 75
 76            repeated -- int
 77
 78            style -- str
 79        """
 80        super().__init__(**kwargs)
 81        self.y = None
 82        if not hasattr(self, "_indexes"):
 83            self._indexes = {}
 84            self._indexes["_rmap"] = {}
 85        if not hasattr(self, "_rmap"):
 86            self._compute_row_cache()
 87            if not hasattr(self, "_tmap"):
 88                self._tmap = []
 89                self._cmap = []
 90        if self._do_init:
 91            if width is not None:
 92                for _i in range(width):
 93                    self.append(Cell())  # type:ignore
 94            if repeated:
 95                self.repeated = repeated
 96            if style is not None:
 97                self.style = style
 98            self._compute_row_cache()
 99
100    def _get_cells(self) -> list[Element]:
101        return self.get_elements(_xpath_cell)
102
103    def _translate_row_coordinates(
104        self,
105        coord: tuple | list | str,
106    ) -> tuple[int | None, int | None]:
107        xyzt = convert_coordinates(coord)
108        if len(xyzt) == 2:
109            x, z = xyzt
110        else:
111            x, _, z, __ = xyzt
112        if x and x < 0:
113            x = increment(x, self.width)
114        if z and z < 0:
115            z = increment(z, self.width)
116        return (x, z)
117
118    def _compute_row_cache(self) -> None:
119        idx_repeated_seq = self.elements_repeated_sequence(
120            _xpath_cell, "table:number-columns-repeated"
121        )
122        self._rmap = make_cache_map(idx_repeated_seq)
123
124    # Public API
125
126    @property
127    def clone(self) -> Row:
128        clone = Element.clone.fget(self)  # type: ignore
129        clone.y = self.y
130        if hasattr(self, "_tmap"):
131            if hasattr(self, "_rmap"):
132                clone._rmap = self._rmap[:]
133            clone._tmap = self._tmap[:]
134            clone._cmap = self._cmap[:]
135        return clone
136
137    def _set_repeated(self, repeated: int | None) -> None:
138        """Internal only. Set the numnber of times the row is repeated, or
139        None to delete it. Without changing cache.
140
141        Arguments:
142
143            repeated -- int
144        """
145        if repeated is None or repeated < 2:
146            with contextlib.suppress(KeyError):
147                self.del_attribute("table:number-rows-repeated")
148            return
149        self.set_attribute("table:number-rows-repeated", str(repeated))
150
151    @property
152    def repeated(self) -> int | None:
153        """Get / set the number of times the row is repeated.
154
155        Always None when using the table API.
156
157        Return: int or None
158        """
159        repeated = self.get_attribute("table:number-rows-repeated")
160        if repeated is None:
161            return None
162        return int(repeated)
163
164    @repeated.setter
165    def repeated(self, repeated: int | None) -> None:
166        self._set_repeated(repeated)
167        # update cache
168        current: Element = self
169        while True:
170            # look for Table, parent may be group of rows
171            upper = current.parent
172            if not upper:
173                # lonely row
174                return
175            # parent may be group of rows, not table
176            if isinstance(upper, Element) and upper._tag == "table:table":
177                break
178            current = upper
179        # fixme : need to optimize this
180        if isinstance(upper, Element) and upper._tag == "table:table":
181            upper._compute_table_cache()
182            if hasattr(self, "_tmap"):
183                del self._tmap[:]
184                self._tmap.extend(upper._tmap)
185            else:
186                self._tmap = upper._tmap
187
188    @property
189    def style(self) -> str | None:
190        """Get /set the style of the row itself.
191
192        Return: str
193        """
194        return self.get_attribute("table:style-name")  # type: ignore
195
196    @style.setter
197    def style(self, style: str | Element) -> None:
198        self.set_style_attribute("table:style-name", style)
199
200    @property
201    def width(self) -> int:
202        """Get the number of expected cells in the row, i.e. addition
203        repetitions.
204
205        Return: int
206        """
207        try:
208            value = self._rmap[-1] + 1
209        except Exception:
210            value = 0
211        return value
212
213    def _translate_x_from_any(self, x: str | int) -> int:
214        return translate_from_any(x, self.width, 0)
215
216    def traverse(  # noqa: C901
217        self,
218        start: int | None = None,
219        end: int | None = None,
220    ) -> Iterator[Cell]:
221        """Yield as many cell elements as expected cells in the row, i.e.
222        expand repetitions by returning the same cell as many times as
223        necessary.
224
225            Arguments:
226
227                start -- int
228
229                end -- int
230
231        Copies are returned, use set_cell() to push them back.
232        """
233        idx = -1
234        before = -1
235        x = 0
236        cell: Cell
237        if start is None and end is None:
238            for juska in self._rmap:
239                idx += 1
240                if idx in self._indexes["_rmap"]:
241                    cell = self._indexes["_rmap"][idx]
242                else:
243                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
244                    if not isinstance(cell, Cell):
245                        raise TypeError(f"Not a cell: {cell!r}")
246                    self._indexes["_rmap"][idx] = cell
247                repeated = juska - before
248                before = juska
249                for _i in range(repeated or 1):
250                    # Return a copy without the now obsolete repetition
251                    if cell is None:
252                        cell = Cell()
253                    else:
254                        cell = cell.clone
255                        if repeated > 1:
256                            cell.repeated = None
257                    cell.y = self.y
258                    cell.x = x
259                    x += 1
260                    yield cell
261        else:
262            if start is None:
263                start = 0
264            start = max(0, start)
265            if end is None:
266                try:
267                    end = self._rmap[-1]
268                except Exception:
269                    end = -1
270            start_map = find_odf_idx(self._rmap, start)
271            if start_map is None:
272                return
273            if start_map > 0:
274                before = self._rmap[start_map - 1]
275            idx = start_map - 1
276            before = start - 1
277            x = start
278            for juska in self._rmap[start_map:]:
279                idx += 1
280                if idx in self._indexes["_rmap"]:
281                    cell = self._indexes["_rmap"][idx]
282                else:
283                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
284                    if not isinstance(cell, Cell):
285                        raise TypeError(f"Not a cell: {cell!r}")
286                    self._indexes["_rmap"][idx] = cell
287                repeated = juska - before
288                before = juska
289                for _i in range(repeated or 1):
290                    if x <= end:
291                        if cell is None:
292                            cell = Cell()
293                        else:
294                            cell = cell.clone
295                            if repeated > 1 or (x == start and start > 0):
296                                cell.repeated = None
297                        cell.y = self.y
298                        cell.x = x
299                        x += 1
300                        yield cell
301
302    def get_cells(
303        self,
304        coord: str | tuple | None = None,
305        style: str | None = None,
306        content: str | None = None,
307        cell_type: str | None = None,
308    ) -> list[Cell]:
309        """Get the list of cells matching the criteria.
310
311        Filter by cell_type, with cell_type 'all' will retrieve cells of any
312        type, aka non empty cells.
313
314        Filter by coordinates will retrieve the amount of cells defined by
315        'coord', minus the other filters.
316
317        Arguments:
318
319            coord -- str or tuple of int : coordinates
320
321            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
322                         'currency', 'percentage' or 'all'
323
324            content -- str regex
325
326            style -- str
327
328        Return: list of Cell
329        """
330        # fixme : not clones ?
331        if coord:
332            x, z = self._translate_row_coordinates(coord)
333        else:
334            x = None
335            z = None
336        if cell_type:
337            cell_type = cell_type.lower().strip()
338        cells: list[Cell] = []
339        for cell in self.traverse(start=x, end=z):
340            # Filter the cells by cell_type
341            if cell_type:
342                ctype = cell.type
343                if not ctype or not (ctype == cell_type or cell_type == "all"):
344                    continue
345            # Filter the cells with the regex
346            if content and not cell.match(content):
347                continue
348            # Filter the cells with the style
349            if style and style != cell.style:
350                continue
351            cells.append(cell)
352        return cells
353
354    def _get_cell2(self, x: int, clone: bool = True) -> Cell | None:
355        if x >= self.width:
356            return Cell()
357        if clone:
358            return self._get_cell2_base(x).clone  # type: ignore
359        else:
360            return self._get_cell2_base(x)
361
362    def _get_cell2_base(self, x: int) -> Cell | None:
363        idx = find_odf_idx(self._rmap, x)
364        cell: Cell
365        if idx is not None:
366            if idx in self._indexes["_rmap"]:
367                cell = self._indexes["_rmap"][idx]
368            else:
369                cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
370                self._indexes["_rmap"][idx] = cell
371            return cell
372        return None
373
374    def get_cell(self, x: int, clone: bool = True) -> Cell | None:
375        """Get the cell at position "x" starting from 0. Alphabetical
376        positions like "D" are accepted.
377
378        A  copy is returned, use set_cell() to push it back.
379
380        Arguments:
381
382            x -- int or str
383
384        Return: Cell | None
385        """
386        x = self._translate_x_from_any(x)
387        cell = self._get_cell2(x, clone=clone)
388        if not cell:
389            return None
390        cell.y = self.y
391        cell.x = x
392        return cell
393
394    def get_value(
395        self,
396        x: int | str,
397        get_type: bool = False,
398    ) -> Any | tuple[Any, str]:
399        """Shortcut to get the value of the cell at position "x".
400        If get_type is True, returns the tuples (value, ODF type).
401
402        If the cell is empty, returns None or (None, None)
403
404        See get_cell() and Cell.get_value().
405        """
406        if get_type:
407            x = self._translate_x_from_any(x)
408            cell = self._get_cell2_base(x)
409            if cell is None:
410                return (None, None)
411            return cell.get_value(get_type=get_type)
412        x = self._translate_x_from_any(x)
413        cell = self._get_cell2_base(x)
414        if cell is None:
415            return None
416        return cell.get_value()
417
418    def set_cell(
419        self,
420        x: int | str,
421        cell: Cell | None = None,
422        clone: bool = True,
423    ) -> Cell:
424        """Push the cell back in the row at position "x" starting from 0.
425        Alphabetical positions like "D" are accepted.
426
427        Arguments:
428
429            x -- int or str
430
431        returns the cell with x and y updated
432        """
433        cell_back: Cell
434        if cell is None:
435            cell = Cell()
436            repeated = 1
437            clone = False
438        else:
439            repeated = cell.repeated or 1
440        x = self._translate_x_from_any(x)
441        # Outside the defined row
442        diff = x - self.width
443        if diff == 0:
444            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
445        elif diff > 0:
446            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
447            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
448        else:
449            # Inside the defined row
450            set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone)
451            cell.x = x
452            cell.y = self.y
453            cell_back = cell
454        return cell_back
455
456    def set_value(
457        self,
458        x: int | str,
459        value: Any,
460        style: str | None = None,
461        cell_type: str | None = None,
462        currency: str | None = None,
463    ) -> None:
464        """Shortcut to set the value of the cell at position "x".
465
466        Arguments:
467
468            x -- int or str
469
470            value -- Python type
471
472            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
473                     'string' or 'time'
474
475            currency -- three-letter str
476
477            style -- str
478
479        See get_cell() and Cell.get_value().
480        """
481        self.set_cell(
482            x,
483            Cell(value, style=style, cell_type=cell_type, currency=currency),
484            clone=False,
485        )
486
487    def insert_cell(
488        self,
489        x: int | str,
490        cell: Cell | None = None,
491        clone: bool = True,
492    ) -> Cell:
493        """Insert the given cell at position "x" starting from 0. If no cell
494        is given, an empty one is created.
495
496        Alphabetical positions like "D" are accepted.
497
498        Do not use when working on a table, use Table.insert_cell().
499
500        Arguments:
501
502            x -- int or str
503
504            cell -- Cell
505
506        returns the cell with x and y updated
507        """
508        cell_back: Cell
509        if cell is None:
510            cell = Cell()
511        x = self._translate_x_from_any(x)
512        # Outside the defined row
513        diff = x - self.width
514        if diff < 0:
515            insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap")
516            cell.x = x
517            cell.y = self.y
518            cell_back = cell
519        elif diff == 0:
520            cell_back = self.append_cell(cell, clone=clone)
521        else:
522            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
523            cell_back = self.append_cell(cell, clone=clone)
524        return cell_back
525
526    def extend_cells(self, cells: Iterable[Cell] | None = None) -> None:
527        if cells is None:
528            cells = []
529        self.extend(cells)
530        self._compute_row_cache()
531
532    def append_cell(
533        self,
534        cell: Cell | None = None,
535        clone: bool = True,
536        _repeated: int | None = None,
537    ) -> Cell:
538        """Append the given cell at the end of the row. Repeated cells are
539        accepted. If no cell is given, an empty one is created.
540
541        Do not use when working on a table, use Table.append_cell().
542
543        Arguments:
544
545            cell -- Cell
546
547            _repeated -- (optional), repeated value of the row
548
549        returns the cell with x and y updated
550        """
551        if cell is None:
552            cell = Cell()
553            clone = False
554        if clone:
555            cell = cell.clone
556        self._append(cell)
557        if _repeated is None:
558            _repeated = cell.repeated or 1
559        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
560        cell.x = self.width - 1
561        cell.y = self.y
562        return cell
563
564    # fix for unit test and typos
565    append = append_cell  # type: ignore
566
567    def delete_cell(self, x: int | str) -> None:
568        """Delete the cell at the given position "x" starting from 0.
569        Alphabetical positions like "D" are accepted.
570
571        Cells on the right will be shifted to the left. In a table, other
572        rows remain unaffected.
573
574        Arguments:
575
576            x -- int or str
577        """
578        x = self._translate_x_from_any(x)
579        if x >= self.width:
580            return
581        delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")
582
583    def get_values(
584        self,
585        coord: str | tuple | None = None,
586        cell_type: str | None = None,
587        complete: bool = False,
588        get_type: bool = False,
589    ) -> list[Any | tuple[Any, Any]]:
590        """Shortcut to get the cell values in this row.
591
592        Filter by cell_type, with cell_type 'all' will retrieve cells of any
593        type, aka non empty cells.
594        If cell_type is used and complete is True, missing values are
595        replaced by None.
596        If cell_type is None, complete is always True : with no cell type
597        queried, get_values() returns None for each empty cell, the length
598        of the list is equal to the length of the row (depending on
599        coordinates use).
600
601        If get_type is True, returns a tuple (value, ODF type of value), or
602        (None, None) for empty cells if complete is True.
603
604        Filter by coordinates will retrieve the amount of cells defined by
605        coordinates with None for empty cells, except when using cell_type.
606
607
608        Arguments:
609
610            coord -- str or tuple of int : coordinates in row
611
612            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
613                         'currency', 'percentage' or 'all'
614
615            complete -- boolean
616
617            get_type -- boolean
618
619        Return: list of Python types, or list of tuples.
620        """
621        if coord:
622            x, z = self._translate_row_coordinates(coord)
623        else:
624            x = None
625            z = None
626        if cell_type:
627            cell_type = cell_type.lower().strip()
628            values: list[Any | tuple[Any, Any]] = []
629            for cell in self.traverse(start=x, end=z):
630                # Filter the cells by cell_type
631                ctype = cell.type
632                if not ctype or not (ctype == cell_type or cell_type == "all"):
633                    if complete:
634                        if get_type:
635                            values.append((None, None))
636                        else:
637                            values.append(None)
638                    continue
639                values.append(cell.get_value(get_type=get_type))
640            return values
641        else:
642            return [
643                cell.get_value(get_type=get_type)
644                for cell in self.traverse(start=x, end=z)
645            ]
646
647    def set_cells(
648        self,
649        cells: list[Cell] | tuple[Cell] | None = None,
650        start: int | str = 0,
651        clone: bool = True,
652    ) -> None:
653        """Set the cells in the row, from the 'start' column.
654        This method does not clear the row, use row.clear() before to start
655        with an empty row.
656
657        Arguments:
658
659            cells -- list of cells
660
661            start -- int or str
662        """
663        if cells is None:
664            cells = []
665        if start is None:
666            start = 0
667        else:
668            start = self._translate_x_from_any(start)
669        if start == 0 and clone is False and (len(cells) >= self.width):
670            self.clear()
671            self.extend_cells(cells)
672        else:
673            x = start
674            for cell in cells:
675                self.set_cell(x, cell, clone=clone)
676                if cell:
677                    x += cell.repeated or 1
678                else:
679                    x += 1
680
681    def set_values(
682        self,
683        values: list[Any],
684        start: int | str = 0,
685        style: str | None = None,
686        cell_type: str | None = None,
687        currency: str | None = None,
688    ) -> None:
689        """Shortcut to set the value of cells in the row, from the 'start'
690        column vith values.
691        This method does not clear the row, use row.clear() before to start
692        with an empty row.
693
694        Arguments:
695
696            values -- list of Python types
697
698            start -- int or str
699
700            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
701                         'currency' or 'percentage'
702
703            currency -- three-letter str
704
705            style -- cell style
706        """
707        # fixme : if values n, n+ are same, use repeat
708        if start is None:
709            start = 0
710        else:
711            start = self._translate_x_from_any(start)
712        if start == 0 and (len(values) >= self.width):
713            self.clear()
714            cells = [
715                Cell(value, style=style, cell_type=cell_type, currency=currency)
716                for value in values
717            ]
718            self.extend_cells(cells)
719        else:
720            x = start
721            for value in values:
722                self.set_cell(
723                    x,
724                    Cell(value, style=style, cell_type=cell_type, currency=currency),
725                    clone=False,
726                )
727                x += 1
728
729    def rstrip(self, aggressive: bool = False) -> None:
730        """Remove *in-place* empty cells at the right of the row. An empty
731        cell has no value but can have style. If "aggressive" is True, style
732        is ignored.
733
734        Arguments:
735
736            aggressive -- bool
737        """
738        for cell in reversed(self._get_cells()):
739            if not cell.is_empty(aggressive=aggressive):  # type: ignore
740                break
741            self.delete(cell)
742        self._compute_row_cache()
743        self._indexes["_rmap"] = {}
744
745    def is_empty(self, aggressive: bool = False) -> bool:
746        """Return whether every cell in the row has no value or the value
747        evaluates to False (empty string), and no style.
748
749        If aggressive is True, empty cells with style are considered empty.
750
751        Arguments:
752
753            aggressive -- bool
754
755        Return: bool
756        """
757        return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells())  # type: ignore

ODF table row "table:table-row"

Row( width: int | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
59    def __init__(
60        self,
61        width: int | None = None,
62        repeated: int | None = None,
63        style: str | None = None,
64        **kwargs: Any,
65    ) -> None:
66        """create a Row, optionally filled with "width" number of cells.
67
68        Rows contain cells, their number determine the number of columns.
69
70        You don't generally have to create rows by hand, use the Table API.
71
72        Arguments:
73
74            width -- int
75
76            repeated -- int
77
78            style -- str
79        """
80        super().__init__(**kwargs)
81        self.y = None
82        if not hasattr(self, "_indexes"):
83            self._indexes = {}
84            self._indexes["_rmap"] = {}
85        if not hasattr(self, "_rmap"):
86            self._compute_row_cache()
87            if not hasattr(self, "_tmap"):
88                self._tmap = []
89                self._cmap = []
90        if self._do_init:
91            if width is not None:
92                for _i in range(width):
93                    self.append(Cell())  # type:ignore
94            if repeated:
95                self.repeated = repeated
96            if style is not None:
97                self.style = style
98            self._compute_row_cache()

create a Row, optionally filled with "width" number of cells.

Rows contain cells, their number determine the number of columns.

You don't generally have to create rows by hand, use the Table API.

Arguments:

width -- int

repeated -- int

style -- str
y
clone: Row
126    @property
127    def clone(self) -> Row:
128        clone = Element.clone.fget(self)  # type: ignore
129        clone.y = self.y
130        if hasattr(self, "_tmap"):
131            if hasattr(self, "_rmap"):
132                clone._rmap = self._rmap[:]
133            clone._tmap = self._tmap[:]
134            clone._cmap = self._cmap[:]
135        return clone
repeated: int | None
151    @property
152    def repeated(self) -> int | None:
153        """Get / set the number of times the row is repeated.
154
155        Always None when using the table API.
156
157        Return: int or None
158        """
159        repeated = self.get_attribute("table:number-rows-repeated")
160        if repeated is None:
161            return None
162        return int(repeated)

Get / set the number of times the row is repeated.

Always None when using the table API.

Return: int or None

style: str | None
188    @property
189    def style(self) -> str | None:
190        """Get /set the style of the row itself.
191
192        Return: str
193        """
194        return self.get_attribute("table:style-name")  # type: ignore

Get /set the style of the row itself.

Return: str

width: int
200    @property
201    def width(self) -> int:
202        """Get the number of expected cells in the row, i.e. addition
203        repetitions.
204
205        Return: int
206        """
207        try:
208            value = self._rmap[-1] + 1
209        except Exception:
210            value = 0
211        return value

Get the number of expected cells in the row, i.e. addition repetitions.

Return: int

def traverse( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Cell]:
216    def traverse(  # noqa: C901
217        self,
218        start: int | None = None,
219        end: int | None = None,
220    ) -> Iterator[Cell]:
221        """Yield as many cell elements as expected cells in the row, i.e.
222        expand repetitions by returning the same cell as many times as
223        necessary.
224
225            Arguments:
226
227                start -- int
228
229                end -- int
230
231        Copies are returned, use set_cell() to push them back.
232        """
233        idx = -1
234        before = -1
235        x = 0
236        cell: Cell
237        if start is None and end is None:
238            for juska in self._rmap:
239                idx += 1
240                if idx in self._indexes["_rmap"]:
241                    cell = self._indexes["_rmap"][idx]
242                else:
243                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
244                    if not isinstance(cell, Cell):
245                        raise TypeError(f"Not a cell: {cell!r}")
246                    self._indexes["_rmap"][idx] = cell
247                repeated = juska - before
248                before = juska
249                for _i in range(repeated or 1):
250                    # Return a copy without the now obsolete repetition
251                    if cell is None:
252                        cell = Cell()
253                    else:
254                        cell = cell.clone
255                        if repeated > 1:
256                            cell.repeated = None
257                    cell.y = self.y
258                    cell.x = x
259                    x += 1
260                    yield cell
261        else:
262            if start is None:
263                start = 0
264            start = max(0, start)
265            if end is None:
266                try:
267                    end = self._rmap[-1]
268                except Exception:
269                    end = -1
270            start_map = find_odf_idx(self._rmap, start)
271            if start_map is None:
272                return
273            if start_map > 0:
274                before = self._rmap[start_map - 1]
275            idx = start_map - 1
276            before = start - 1
277            x = start
278            for juska in self._rmap[start_map:]:
279                idx += 1
280                if idx in self._indexes["_rmap"]:
281                    cell = self._indexes["_rmap"][idx]
282                else:
283                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
284                    if not isinstance(cell, Cell):
285                        raise TypeError(f"Not a cell: {cell!r}")
286                    self._indexes["_rmap"][idx] = cell
287                repeated = juska - before
288                before = juska
289                for _i in range(repeated or 1):
290                    if x <= end:
291                        if cell is None:
292                            cell = Cell()
293                        else:
294                            cell = cell.clone
295                            if repeated > 1 or (x == start and start > 0):
296                                cell.repeated = None
297                        cell.y = self.y
298                        cell.x = x
299                        x += 1
300                        yield cell

Yield as many cell elements as expected cells in the row, i.e. expand repetitions by returning the same cell as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_cell() to push them back.

def get_cells( self, coord: str | tuple | None = None, style: str | None = None, content: str | None = None, cell_type: str | None = None) -> list[Cell]:
302    def get_cells(
303        self,
304        coord: str | tuple | None = None,
305        style: str | None = None,
306        content: str | None = None,
307        cell_type: str | None = None,
308    ) -> list[Cell]:
309        """Get the list of cells matching the criteria.
310
311        Filter by cell_type, with cell_type 'all' will retrieve cells of any
312        type, aka non empty cells.
313
314        Filter by coordinates will retrieve the amount of cells defined by
315        'coord', minus the other filters.
316
317        Arguments:
318
319            coord -- str or tuple of int : coordinates
320
321            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
322                         'currency', 'percentage' or 'all'
323
324            content -- str regex
325
326            style -- str
327
328        Return: list of Cell
329        """
330        # fixme : not clones ?
331        if coord:
332            x, z = self._translate_row_coordinates(coord)
333        else:
334            x = None
335            z = None
336        if cell_type:
337            cell_type = cell_type.lower().strip()
338        cells: list[Cell] = []
339        for cell in self.traverse(start=x, end=z):
340            # Filter the cells by cell_type
341            if cell_type:
342                ctype = cell.type
343                if not ctype or not (ctype == cell_type or cell_type == "all"):
344                    continue
345            # Filter the cells with the regex
346            if content and not cell.match(content):
347                continue
348            # Filter the cells with the style
349            if style and style != cell.style:
350                continue
351            cells.append(cell)
352        return cells

Get the list of cells matching the criteria.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.

Filter by coordinates will retrieve the amount of cells defined by 'coord', minus the other filters.

Arguments:

coord -- str or tuple of int : coordinates

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

Return: list of Cell

def get_cell(self, x: int, clone: bool = True) -> Cell | None:
374    def get_cell(self, x: int, clone: bool = True) -> Cell | None:
375        """Get the cell at position "x" starting from 0. Alphabetical
376        positions like "D" are accepted.
377
378        A  copy is returned, use set_cell() to push it back.
379
380        Arguments:
381
382            x -- int or str
383
384        Return: Cell | None
385        """
386        x = self._translate_x_from_any(x)
387        cell = self._get_cell2(x, clone=clone)
388        if not cell:
389            return None
390        cell.y = self.y
391        cell.x = x
392        return cell

Get the cell at position "x" starting from 0. Alphabetical positions like "D" are accepted.

A copy is returned, use set_cell() to push it back.

Arguments:

x -- int or str

Return: Cell | None

def get_value( self, x: int | str, get_type: bool = False) -> typing.Any | tuple[typing.Any, str]:
394    def get_value(
395        self,
396        x: int | str,
397        get_type: bool = False,
398    ) -> Any | tuple[Any, str]:
399        """Shortcut to get the value of the cell at position "x".
400        If get_type is True, returns the tuples (value, ODF type).
401
402        If the cell is empty, returns None or (None, None)
403
404        See get_cell() and Cell.get_value().
405        """
406        if get_type:
407            x = self._translate_x_from_any(x)
408            cell = self._get_cell2_base(x)
409            if cell is None:
410                return (None, None)
411            return cell.get_value(get_type=get_type)
412        x = self._translate_x_from_any(x)
413        cell = self._get_cell2_base(x)
414        if cell is None:
415            return None
416        return cell.get_value()

Shortcut to get the value of the cell at position "x". If get_type is True, returns the tuples (value, ODF type).

If the cell is empty, returns None or (None, None)

See get_cell() and Cell.get_value().

def set_cell( self, x: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
418    def set_cell(
419        self,
420        x: int | str,
421        cell: Cell | None = None,
422        clone: bool = True,
423    ) -> Cell:
424        """Push the cell back in the row at position "x" starting from 0.
425        Alphabetical positions like "D" are accepted.
426
427        Arguments:
428
429            x -- int or str
430
431        returns the cell with x and y updated
432        """
433        cell_back: Cell
434        if cell is None:
435            cell = Cell()
436            repeated = 1
437            clone = False
438        else:
439            repeated = cell.repeated or 1
440        x = self._translate_x_from_any(x)
441        # Outside the defined row
442        diff = x - self.width
443        if diff == 0:
444            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
445        elif diff > 0:
446            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
447            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
448        else:
449            # Inside the defined row
450            set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone)
451            cell.x = x
452            cell.y = self.y
453            cell_back = cell
454        return cell_back

Push the cell back in the row at position "x" starting from 0. Alphabetical positions like "D" are accepted.

Arguments:

x -- int or str

returns the cell with x and y updated

def set_value( self, x: int | str, value: Any, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
456    def set_value(
457        self,
458        x: int | str,
459        value: Any,
460        style: str | None = None,
461        cell_type: str | None = None,
462        currency: str | None = None,
463    ) -> None:
464        """Shortcut to set the value of the cell at position "x".
465
466        Arguments:
467
468            x -- int or str
469
470            value -- Python type
471
472            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
473                     'string' or 'time'
474
475            currency -- three-letter str
476
477            style -- str
478
479        See get_cell() and Cell.get_value().
480        """
481        self.set_cell(
482            x,
483            Cell(value, style=style, cell_type=cell_type, currency=currency),
484            clone=False,
485        )

Shortcut to set the value of the cell at position "x".

Arguments:

x -- int or str

value -- Python type

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
         'string' or 'time'

currency -- three-letter str

style -- str

See get_cell() and Cell.get_value().

def insert_cell( self, x: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
487    def insert_cell(
488        self,
489        x: int | str,
490        cell: Cell | None = None,
491        clone: bool = True,
492    ) -> Cell:
493        """Insert the given cell at position "x" starting from 0. If no cell
494        is given, an empty one is created.
495
496        Alphabetical positions like "D" are accepted.
497
498        Do not use when working on a table, use Table.insert_cell().
499
500        Arguments:
501
502            x -- int or str
503
504            cell -- Cell
505
506        returns the cell with x and y updated
507        """
508        cell_back: Cell
509        if cell is None:
510            cell = Cell()
511        x = self._translate_x_from_any(x)
512        # Outside the defined row
513        diff = x - self.width
514        if diff < 0:
515            insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap")
516            cell.x = x
517            cell.y = self.y
518            cell_back = cell
519        elif diff == 0:
520            cell_back = self.append_cell(cell, clone=clone)
521        else:
522            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
523            cell_back = self.append_cell(cell, clone=clone)
524        return cell_back

Insert the given cell at position "x" starting from 0. If no cell is given, an empty one is created.

Alphabetical positions like "D" are accepted.

Do not use when working on a table, use Table.insert_cell().

Arguments:

x -- int or str

cell -- Cell

returns the cell with x and y updated

def extend_cells( self, cells: collections.abc.Iterable[Cell] | None = None) -> None:
526    def extend_cells(self, cells: Iterable[Cell] | None = None) -> None:
527        if cells is None:
528            cells = []
529        self.extend(cells)
530        self._compute_row_cache()
def append_cell( self, cell: Cell | None = None, clone: bool = True, _repeated: int | None = None) -> Cell:
532    def append_cell(
533        self,
534        cell: Cell | None = None,
535        clone: bool = True,
536        _repeated: int | None = None,
537    ) -> Cell:
538        """Append the given cell at the end of the row. Repeated cells are
539        accepted. If no cell is given, an empty one is created.
540
541        Do not use when working on a table, use Table.append_cell().
542
543        Arguments:
544
545            cell -- Cell
546
547            _repeated -- (optional), repeated value of the row
548
549        returns the cell with x and y updated
550        """
551        if cell is None:
552            cell = Cell()
553            clone = False
554        if clone:
555            cell = cell.clone
556        self._append(cell)
557        if _repeated is None:
558            _repeated = cell.repeated or 1
559        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
560        cell.x = self.width - 1
561        cell.y = self.y
562        return cell

Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.

Do not use when working on a table, use Table.append_cell().

Arguments:

cell -- Cell

_repeated -- (optional), repeated value of the row

returns the cell with x and y updated

def append( self, cell: Cell | None = None, clone: bool = True, _repeated: int | None = None) -> Cell:
532    def append_cell(
533        self,
534        cell: Cell | None = None,
535        clone: bool = True,
536        _repeated: int | None = None,
537    ) -> Cell:
538        """Append the given cell at the end of the row. Repeated cells are
539        accepted. If no cell is given, an empty one is created.
540
541        Do not use when working on a table, use Table.append_cell().
542
543        Arguments:
544
545            cell -- Cell
546
547            _repeated -- (optional), repeated value of the row
548
549        returns the cell with x and y updated
550        """
551        if cell is None:
552            cell = Cell()
553            clone = False
554        if clone:
555            cell = cell.clone
556        self._append(cell)
557        if _repeated is None:
558            _repeated = cell.repeated or 1
559        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
560        cell.x = self.width - 1
561        cell.y = self.y
562        return cell

Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.

Do not use when working on a table, use Table.append_cell().

Arguments:

cell -- Cell

_repeated -- (optional), repeated value of the row

returns the cell with x and y updated

def delete_cell(self, x: int | str) -> None:
567    def delete_cell(self, x: int | str) -> None:
568        """Delete the cell at the given position "x" starting from 0.
569        Alphabetical positions like "D" are accepted.
570
571        Cells on the right will be shifted to the left. In a table, other
572        rows remain unaffected.
573
574        Arguments:
575
576            x -- int or str
577        """
578        x = self._translate_x_from_any(x)
579        if x >= self.width:
580            return
581        delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")

Delete the cell at the given position "x" starting from 0. Alphabetical positions like "D" are accepted.

Cells on the right will be shifted to the left. In a table, other rows remain unaffected.

Arguments:

x -- int or str
def get_values( self, coord: str | tuple | None = None, cell_type: str | None = None, complete: bool = False, get_type: bool = False) -> list[typing.Any | tuple[typing.Any, typing.Any]]:
583    def get_values(
584        self,
585        coord: str | tuple | None = None,
586        cell_type: str | None = None,
587        complete: bool = False,
588        get_type: bool = False,
589    ) -> list[Any | tuple[Any, Any]]:
590        """Shortcut to get the cell values in this row.
591
592        Filter by cell_type, with cell_type 'all' will retrieve cells of any
593        type, aka non empty cells.
594        If cell_type is used and complete is True, missing values are
595        replaced by None.
596        If cell_type is None, complete is always True : with no cell type
597        queried, get_values() returns None for each empty cell, the length
598        of the list is equal to the length of the row (depending on
599        coordinates use).
600
601        If get_type is True, returns a tuple (value, ODF type of value), or
602        (None, None) for empty cells if complete is True.
603
604        Filter by coordinates will retrieve the amount of cells defined by
605        coordinates with None for empty cells, except when using cell_type.
606
607
608        Arguments:
609
610            coord -- str or tuple of int : coordinates in row
611
612            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
613                         'currency', 'percentage' or 'all'
614
615            complete -- boolean
616
617            get_type -- boolean
618
619        Return: list of Python types, or list of tuples.
620        """
621        if coord:
622            x, z = self._translate_row_coordinates(coord)
623        else:
624            x = None
625            z = None
626        if cell_type:
627            cell_type = cell_type.lower().strip()
628            values: list[Any | tuple[Any, Any]] = []
629            for cell in self.traverse(start=x, end=z):
630                # Filter the cells by cell_type
631                ctype = cell.type
632                if not ctype or not (ctype == cell_type or cell_type == "all"):
633                    if complete:
634                        if get_type:
635                            values.append((None, None))
636                        else:
637                            values.append(None)
638                    continue
639                values.append(cell.get_value(get_type=get_type))
640            return values
641        else:
642            return [
643                cell.get_value(get_type=get_type)
644                for cell in self.traverse(start=x, end=z)
645            ]

Shortcut to get the cell values in this row.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type is used and complete is True, missing values are replaced by None. If cell_type is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length of the list is equal to the length of the row (depending on coordinates use).

If get_type is True, returns a tuple (value, ODF type of value), or (None, None) for empty cells if complete is True.

Filter by coordinates will retrieve the amount of cells defined by coordinates with None for empty cells, except when using cell_type.

Arguments:

coord -- str or tuple of int : coordinates in row

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of Python types, or list of tuples.

def set_cells( self, cells: list[Cell] | tuple[Cell] | None = None, start: int | str = 0, clone: bool = True) -> None:
647    def set_cells(
648        self,
649        cells: list[Cell] | tuple[Cell] | None = None,
650        start: int | str = 0,
651        clone: bool = True,
652    ) -> None:
653        """Set the cells in the row, from the 'start' column.
654        This method does not clear the row, use row.clear() before to start
655        with an empty row.
656
657        Arguments:
658
659            cells -- list of cells
660
661            start -- int or str
662        """
663        if cells is None:
664            cells = []
665        if start is None:
666            start = 0
667        else:
668            start = self._translate_x_from_any(start)
669        if start == 0 and clone is False and (len(cells) >= self.width):
670            self.clear()
671            self.extend_cells(cells)
672        else:
673            x = start
674            for cell in cells:
675                self.set_cell(x, cell, clone=clone)
676                if cell:
677                    x += cell.repeated or 1
678                else:
679                    x += 1

Set the cells in the row, from the 'start' column. This method does not clear the row, use row.clear() before to start with an empty row.

Arguments:

cells -- list of cells

start -- int or str
def set_values( self, values: list[typing.Any], start: int | str = 0, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
681    def set_values(
682        self,
683        values: list[Any],
684        start: int | str = 0,
685        style: str | None = None,
686        cell_type: str | None = None,
687        currency: str | None = None,
688    ) -> None:
689        """Shortcut to set the value of cells in the row, from the 'start'
690        column vith values.
691        This method does not clear the row, use row.clear() before to start
692        with an empty row.
693
694        Arguments:
695
696            values -- list of Python types
697
698            start -- int or str
699
700            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
701                         'currency' or 'percentage'
702
703            currency -- three-letter str
704
705            style -- cell style
706        """
707        # fixme : if values n, n+ are same, use repeat
708        if start is None:
709            start = 0
710        else:
711            start = self._translate_x_from_any(start)
712        if start == 0 and (len(values) >= self.width):
713            self.clear()
714            cells = [
715                Cell(value, style=style, cell_type=cell_type, currency=currency)
716                for value in values
717            ]
718            self.extend_cells(cells)
719        else:
720            x = start
721            for value in values:
722                self.set_cell(
723                    x,
724                    Cell(value, style=style, cell_type=cell_type, currency=currency),
725                    clone=False,
726                )
727                x += 1

Shortcut to set the value of cells in the row, from the 'start' column vith values. This method does not clear the row, use row.clear() before to start with an empty row.

Arguments:

values -- list of Python types

start -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency' or 'percentage'

currency -- three-letter str

style -- cell style
def rstrip(self, aggressive: bool = False) -> None:
729    def rstrip(self, aggressive: bool = False) -> None:
730        """Remove *in-place* empty cells at the right of the row. An empty
731        cell has no value but can have style. If "aggressive" is True, style
732        is ignored.
733
734        Arguments:
735
736            aggressive -- bool
737        """
738        for cell in reversed(self._get_cells()):
739            if not cell.is_empty(aggressive=aggressive):  # type: ignore
740                break
741            self.delete(cell)
742        self._compute_row_cache()
743        self._indexes["_rmap"] = {}

Remove in-place empty cells at the right of the row. An empty cell has no value but can have style. If "aggressive" is True, style is ignored.

Arguments:

aggressive -- bool
def is_empty(self, aggressive: bool = False) -> bool:
745    def is_empty(self, aggressive: bool = False) -> bool:
746        """Return whether every cell in the row has no value or the value
747        evaluates to False (empty string), and no style.
748
749        If aggressive is True, empty cells with style are considered empty.
750
751        Arguments:
752
753            aggressive -- bool
754
755        Return: bool
756        """
757        return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells())  # type: ignore

Return whether every cell in the row has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool

Return: bool

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class RowGroup(odfdo.Element):
133class RowGroup(Element):
134    """ "table:table-row-group" group rows with common properties."""
135
136    # TODO
137    _tag = "table:table-row-group"
138    _caching = True
139
140    def __init__(
141        self,
142        height: int | None = None,
143        width: int | None = None,
144        **kwargs: Any,
145    ) -> None:
146        """Create a group of rows, optionnaly filled with "height" number of
147        rows, of "width" cells each.
148
149        Row group bear style information applied to a series of rows.
150
151        Arguments:
152
153            height -- int
154
155            width -- int
156        """
157        super().__init__(**kwargs)
158        if self._do_init and height is not None:
159            for _i in range(height):
160                row = Row(width=width)
161                self.append(row)

"table:table-row-group" group rows with common properties.

RowGroup(height: int | None = None, width: int | None = None, **kwargs: Any)
140    def __init__(
141        self,
142        height: int | None = None,
143        width: int | None = None,
144        **kwargs: Any,
145    ) -> None:
146        """Create a group of rows, optionnaly filled with "height" number of
147        rows, of "width" cells each.
148
149        Row group bear style information applied to a series of rows.
150
151        Arguments:
152
153            height -- int
154
155            width -- int
156        """
157        super().__init__(**kwargs)
158        if self._do_init and height is not None:
159            for _i in range(height):
160                row = Row(width=width)
161                self.append(row)

Create a group of rows, optionnaly filled with "height" number of rows, of "width" cells each.

Row group bear style information applied to a series of rows.

Arguments:

height -- int

width -- int
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Section(odfdo.Element):
32class Section(Element):
33    """ODF section "text:section"
34
35    Arguments:
36
37        style -- str
38
39        name -- str
40    """
41
42    _tag = "text:section"
43    _properties = (
44        PropDef("style", "text:style-name"),
45        PropDef("name", "text:name"),
46    )
47
48    def __init__(
49        self,
50        style: str | None = None,
51        name: str | None = None,
52        **kwargs: Any,
53    ) -> None:
54        super().__init__(**kwargs)
55        if self._do_init:
56            if style:
57                self.style = style
58            if name:
59                self.name = name
60
61    def get_formatted_text(self, context: dict | None = None) -> str:
62        result = [element.get_formatted_text(context) for element in self.children]
63        result.append("\n")
64        return "".join(result)

ODF section "text:section"

Arguments:

style -- str

name -- str
Section(style: str | None = None, name: str | None = None, **kwargs: Any)
48    def __init__(
49        self,
50        style: str | None = None,
51        name: str | None = None,
52        **kwargs: Any,
53    ) -> None:
54        super().__init__(**kwargs)
55        if self._do_init:
56            if style:
57                self.style = style
58            if name:
59                self.name = name
def get_formatted_text(self, context: dict | None = None) -> str:
61    def get_formatted_text(self, context: dict | None = None) -> str:
62        result = [element.get_formatted_text(context) for element in self.children]
63        result.append("\n")
64        return "".join(result)

This function should return a beautiful version of the text.

style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Spacer(odfdo.Element):
169class Spacer(Element):
170    """This element shall be used to represent the second and all following “ “
171    (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters.
172    Note: It is not an error if the character preceding the element is not a
173    white space character, but it is good practice to use this element only for
174    the second and all following SPACE characters in a sequence.
175    """
176
177    _tag = "text:s"
178    _properties: tuple[PropDef, ...] = (PropDef("number", "text:c"),)
179
180    def __init__(self, number: int = 1, **kwargs: Any):
181        """
182        Arguments:
183
184            number -- int
185        """
186        super().__init__(**kwargs)
187        if self._do_init:
188            self.number = str(number)

This element shall be used to represent the second and all following “ “ (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. Note: It is not an error if the character preceding the element is not a white space character, but it is good practice to use this element only for the second and all following SPACE characters in a sequence.

Spacer(number: int = 1, **kwargs: Any)
180    def __init__(self, number: int = 1, **kwargs: Any):
181        """
182        Arguments:
183
184            number -- int
185        """
186        super().__init__(**kwargs)
187        if self._do_init:
188            self.number = str(number)

Arguments:

number -- int
number: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Span(odfdo.Paragraph):
799class Span(Paragraph):
800    """Create a span element "text:span" of the given style containing the optional
801    given text.
802    """
803
804    _tag = "text:span"
805    _properties = (
806        PropDef("style", "text:style-name"),
807        PropDef("class_names", "text:class-names"),
808    )
809
810    def __init__(
811        self,
812        text: str | None = None,
813        style: str | None = None,
814        **kwargs: Any,
815    ) -> None:
816        """
817        Arguments:
818
819            text -- str
820
821            style -- str
822        """
823        super().__init__(**kwargs)
824        if self._do_init:
825            if text:
826                self.text = text
827            if style:
828                self.style = style

Create a span element "text:span" of the given style containing the optional given text.

Span(text: str | None = None, style: str | None = None, **kwargs: Any)
810    def __init__(
811        self,
812        text: str | None = None,
813        style: str | None = None,
814        **kwargs: Any,
815    ) -> None:
816        """
817        Arguments:
818
819            text -- str
820
821            style -- str
822        """
823        super().__init__(**kwargs)
824        if self._do_init:
825            if text:
826                self.text = text
827            if style:
828                self.style = style

Arguments:

text -- str

style -- str
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
class_names: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Paragraph
insert_note
insert_annotation
insert_annotation_end
set_reference_mark
set_reference_mark_end
insert_variable
set_span
remove_spans
remove_span
insert_reference
set_bookmark
odfdo.paragraph_base.ParagraphBase
get_formatted_text
append_plain_text
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Style(odfdo.Element):
285class Style(Element):
286    """Style class for all these tags:
287
288    'style:style'
289    'number:date-style',
290    'number:number-style',
291    'number:percentage-style',
292    'number:time-style'
293    'style:font-face',
294    'style:master-page',
295    'style:page-layout',
296    'style:presentation-page-layout',
297    'text:list-style',
298    'text:outline-style',
299    'style:tab-stops',
300    ...
301    """
302
303    _properties: tuple[PropDef, ...] = (
304        PropDef("page_layout", "style:page-layout-name", "master-page"),
305        PropDef("next_style", "style:next-style-name", "master-page"),
306        PropDef("name", "style:name"),
307        PropDef("parent_style", "style:parent-style-name"),
308        PropDef("display_name", "style:display-name"),
309        PropDef("svg_font_family", "svg:font-family"),
310        PropDef("font_family_generic", "style:font-family-generic"),
311        PropDef("font_pitch", "style:font-pitch"),
312        PropDef("text_style", "text:style-name"),
313        PropDef("master_page", "style:master-page-name", "paragraph"),
314        PropDef("master_page", "style:master-page-name", "paragraph"),
315        PropDef("master_page", "style:master-page-name", "paragraph"),
316        # style:tab-stop
317        PropDef("style_type", "style:type"),
318        PropDef("leader_style", "style:leader-style"),
319        PropDef("leader_text", "style:leader-text"),
320        PropDef("style_position", "style:position"),
321        PropDef("leader_text", "style:position"),
322    )
323
324    def __init__(  # noqa: C901
325        self,
326        family: str | None = None,
327        name: str | None = None,
328        display_name: str | None = None,
329        parent_style: str | None = None,
330        # Where properties apply
331        area: str | None = None,
332        # For family 'text':
333        color: str | tuple | None = None,
334        background_color: str | tuple | None = None,
335        italic: bool = False,
336        bold: bool = False,
337        # For family 'paragraph'
338        master_page: str | None = None,
339        # For family 'master-page'
340        page_layout: str | None = None,
341        next_style: str | None = None,
342        # For family 'table-cell'
343        data_style: str | None = None,  # unused
344        border: str | None = None,
345        border_top: str | None = None,
346        border_right: str | None = None,
347        border_bottom: str | None = None,
348        border_left: str | None = None,
349        padding: str | None = None,
350        padding_top: str | None = None,
351        padding_bottom: str | None = None,
352        padding_left: str | None = None,
353        padding_right: str | None = None,
354        shadow: str | None = None,
355        # For family 'table-row'
356        height: str | None = None,
357        use_optimal_height: bool = False,
358        # For family 'table-column'
359        width: str | None = None,
360        break_before: str | None = None,
361        break_after: str | None = None,
362        # For family 'graphic'
363        min_height: str | None = None,
364        # For family 'font-face'
365        font_name: str | None = None,
366        font_family: str | None = None,
367        font_family_generic: str | None = None,
368        font_pitch: str = "variable",
369        # Every other property
370        **kwargs: Any,
371    ) -> None:
372        """Create a style of the given family. The name is not mandatory at this
373        point but will become required when inserting in a document as a common
374        style.
375
376        The display name is the name the user sees in an office application.
377
378        The parent_style is the name of the style this style will inherit from.
379
380        To set properties, pass them as keyword arguments. The area properties
381        apply to is optional and defaults to the family.
382
383        Arguments:
384
385            family -- 'paragraph', 'text', 'section', 'table', 'table-column',
386                      'table-row', 'table-cell', 'table-page', 'chart',
387                      'drawing-page', 'graphic', 'presentation',
388                      'control', 'ruby', 'list', 'number', 'page-layout'
389                      'font-face', or 'master-page'
390
391            name -- str
392
393            display_name -- str
394
395            parent_style -- str
396
397            area -- str
398
399        'text' Properties:
400
401            italic -- bool
402
403            bold -- bool
404
405        'paragraph' Properties:
406
407            master_page -- str
408
409        'master-page' Properties:
410
411            page_layout -- str
412
413            next_style -- str
414
415        'table-cell' Properties:
416
417            border, border_top, border_right, border_bottom, border_left -- str,
418            e.g. "0.002cm solid #000000" or 'none'
419
420            padding, padding_top, padding_right, padding_bottom, padding_left -- str,
421            e.g. "0.002cm" or 'none'
422
423            shadow -- str, e.g. "#808080 0.176cm 0.176cm"
424
425        'table-row' Properties:
426
427            height -- str, e.g. '5cm'
428
429            use_optimal_height -- bool
430
431        'table-column' Properties:
432
433            width -- str, e.g. '5cm'
434
435            break_before -- 'page', 'column' or 'auto'
436
437            break_after -- 'page', 'column' or 'auto'
438        """
439        self._family: str | None = None
440        tag_or_elem = kwargs.get("tag_or_elem", None)
441        if tag_or_elem is None:
442            family = to_str(family)
443            if family not in FAMILY_MAPPING:
444                raise ValueError("Unknown family value: %s" % family)
445            kwargs["tag"] = FAMILY_MAPPING[family]
446        super().__init__(**kwargs)
447        if self._do_init and family not in SUBCLASSED_STYLES:
448            kwargs.pop("tag", None)
449            kwargs.pop("tag_or_elem", None)
450            self.family = family  # relevant test made by property
451            # Common attributes
452            if name:
453                self.name = name
454            if display_name:
455                self.display_name = display_name
456            if parent_style:
457                self.parent_style = parent_style
458            # Paragraph
459            if family == "paragraph":
460                if master_page:
461                    self.master_page = master_page
462            # Master Page
463            elif family == "master-page":
464                if page_layout:
465                    self.page_layout = page_layout
466                if next_style:
467                    self.next_style = next_style
468            # Font face
469            elif family == "font-face":
470                if not font_name:
471                    raise ValueError("A font_name is required for 'font-face' style")
472                self.set_font(
473                    font_name,
474                    family=font_family,
475                    family_generic=font_family_generic,
476                    pitch=font_pitch,
477                )
478            # Properties
479            if area is None:
480                area = family
481            area = to_str(area)
482            # Text
483            if area == "text":
484                if color:
485                    kwargs["fo:color"] = color
486                if background_color:
487                    kwargs["fo:background-color"] = background_color
488                if italic:
489                    kwargs["fo:font-style"] = "italic"
490                    kwargs["style:font-style-asian"] = "italic"
491                    kwargs["style:font-style-complex"] = "italic"
492                if bold:
493                    kwargs["fo:font-weight"] = "bold"
494                    kwargs["style:font-weight-asian"] = "bold"
495                    kwargs["style:font-weight-complex"] = "bold"
496            # Table cell
497            elif area == "table-cell":
498                if border:
499                    kwargs["fo:border"] = border
500                elif border_top or border_right or border_bottom or border_left:
501                    kwargs["fo:border-top"] = border_top or "none"
502                    kwargs["fo:border-right"] = border_right or "none"
503                    kwargs["fo:border-bottom"] = border_bottom or "none"
504                    kwargs["fo:border-left"] = border_left or "none"
505                else:  # no border_top, ... neither border are defined
506                    pass  # left untouched
507                if padding:
508                    kwargs["fo:padding"] = padding
509                elif padding_top or padding_right or padding_bottom or padding_left:
510                    kwargs["fo:padding-top"] = padding_top or "none"
511                    kwargs["fo:padding-right"] = padding_right or "none"
512                    kwargs["fo:padding-bottom"] = padding_bottom or "none"
513                    kwargs["fo:padding-left"] = padding_left or "none"
514                else:  # no border_top, ... neither border are defined
515                    pass  # left untouched
516                if shadow:
517                    kwargs["style:shadow"] = shadow
518                if background_color:
519                    kwargs["fo:background-color"] = background_color
520            # Table row
521            elif area == "table-row":
522                if height:
523                    kwargs["style:row-height"] = height
524                if use_optimal_height:
525                    kwargs["style:use-optimal-row-height"] = Boolean.encode(
526                        use_optimal_height
527                    )
528                if background_color:
529                    kwargs["fo:background-color"] = background_color
530            # Table column
531            elif area == "table-column":
532                if width:
533                    kwargs["style:column-width"] = width
534                if break_before:
535                    kwargs["fo:break-before"] = break_before
536                if break_after:
537                    kwargs["fo:break-after"] = break_after
538            # Graphic
539            elif area == "graphic":
540                if min_height:
541                    kwargs["fo:min-height"] = min_height
542            # Every other properties
543            if kwargs:
544                self.set_properties(kwargs, area=area)
545
546    @property
547    def family(self) -> str | None:
548        if self._family is None:
549            self._family = FALSE_FAMILY_MAP_REVERSE.get(
550                self.tag, self.get_attribute_string("style:family")
551            )
552        return self._family
553
554    @family.setter
555    def family(self, family: str | None) -> None:
556        self._family = family
557        if family in FAMILY_ODF_STD and self.tag == "style:style":
558            self.set_attribute("style:family", family)
559
560    def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
561        """Get the mapping of all properties of this style. By default the
562        properties of the same family, e.g. a paragraph style and its
563        paragraph properties. Specify the area to get the text properties of
564        a paragraph style for example.
565
566        Arguments:
567
568            area -- str
569
570        Return: dict
571        """
572        if area is None:
573            area = self.family
574        element = self.get_element(f"style:{area}-properties")
575        if element is None:
576            return None
577        properties: dict[str, str | dict] = element.attributes  # type: ignore
578        # Nested properties are nested dictionaries
579        for child in element.children:
580            properties[child.tag] = child.attributes
581        return properties
582
583    def set_properties(  # noqa: C901
584        self,
585        properties: dict[str, str | dict] | None = None,
586        style: Style | None = None,
587        area: str | None = None,
588        **kwargs: Any,
589    ) -> None:
590        """Set the properties of the "area" type of this style. Properties
591        are given either as a dict or as named arguments (or both). The area
592        is identical to the style family by default. If the properties
593        element is missing, it is created.
594
595        Instead of properties, you can pass a style with properties of the
596        same area. These will be copied.
597
598        Arguments:
599
600            properties -- dict
601
602            style -- Style
603
604            area -- 'paragraph', 'text'...
605        """
606        if properties is None:
607            properties = {}
608        if area is None:
609            if isinstance(self.family, bool):
610                area = None
611            else:
612                area = self.family
613        element = self.get_element(f"style:{area}-properties")
614        if element is None:
615            element = Element.from_tag(f"style:{area}-properties")
616            self.append(element)
617        if properties or kwargs:
618            properties = _expand_properties_dict(_merge_dicts(properties, kwargs))
619        elif style is not None:
620            properties = style.get_properties(area=area)
621            if properties is None:
622                return
623        if properties is None:
624            return
625        for key, value in properties.items():
626            if value is None:
627                element.del_attribute(key)
628            elif isinstance(value, (str, bool, tuple)):
629                element.set_attribute(key, value)
630            else:
631                pass
632
633    def del_properties(
634        self,
635        properties: list[str] | None = None,
636        area: str | None = None,
637    ) -> None:
638        """Delete the given properties, either by list argument or
639        positional argument (or both). Remove only from the given area,
640        identical to the style family by default.
641
642        Arguments:
643
644            properties -- list
645
646            area -- str
647        """
648        if properties is None:
649            properties = []
650        if area is None:
651            area = self.family
652        element = self.get_element(f"style:{area}-properties")
653        if element is None:
654            raise ValueError(
655                f"properties element is inexistent for: style:{area}-properties"
656            )
657        for key in _expand_properties_list(properties):
658            element.del_attribute(key)
659
660    def set_background(  # noqa: C901
661        self,
662        color: str | None = None,
663        url: str | None = None,
664        position: str | None = "center",
665        repeat: str | None = None,
666        opacity: str | None = None,
667        filter: str | None = None,  # noqa: A002
668    ) -> None:
669        """Set the background color of a text style, or the background color
670        or image of a paragraph style or page layout.
671
672        With no argument, remove any existing background.
673
674        The position is one or two of 'center', 'left', 'right', 'top' or
675        'bottom'.
676
677        The repeat is 'no-repeat', 'repeat' or 'stretch'.
678
679        The opacity is a percentage integer (not a string with the '%s' sign)
680
681        The filter is an application-specific filter name defined elsewhere.
682
683        Though this method is defined on the base style class, it will raise
684        an error if the style type is not compatible.
685
686        Arguments:
687
688            color -- '#rrggbb'
689
690            url -- str
691
692            position -- str
693
694            repeat -- str
695
696            opacity -- int
697
698            filter -- str
699        """
700        family = self.family
701        if family not in {
702            "text",
703            "paragraph",
704            "page-layout",
705            "section",
706            "table",
707            "table-row",
708            "table-cell",
709            "graphic",
710        }:
711            raise TypeError("No background support for this family")
712        if url is not None and family == "text":
713            raise TypeError("No background image for text styles")
714        properties = self.get_element(f"style:{family}-properties")
715        bg_image: BackgroundImage | None = None
716        if properties is not None:
717            bg_image = properties.get_element("style:background-image")  # type:ignore
718        # Erasing
719        if color is None and url is None:
720            if properties is None:
721                return
722            properties.del_attribute("fo:background-color")
723            if bg_image is not None:
724                properties.delete(bg_image)
725            return
726        # Add the properties if necessary
727        if properties is None:
728            properties = Element.from_tag(f"style:{family}-properties")
729            self.append(properties)
730        # Add the color...
731        if color:
732            properties.set_attribute("fo:background-color", color)
733            if bg_image is not None:
734                properties.delete(bg_image)
735        # ... or the background
736        elif url:
737            properties.set_attribute("fo:background-color", "transparent")
738            if bg_image is None:
739                bg_image = Element.from_tag("style:background-image")  # type:ignore
740                properties.append(bg_image)  # type:ignore
741            bg_image.url = url  # type:ignore
742            if position:
743                bg_image.position = position  # type:ignore
744            if repeat:
745                bg_image.repeat = repeat  # type:ignore
746            if opacity:
747                bg_image.opacity = opacity  # type:ignore
748            if filter:
749                bg_image.filter = filter  # type:ignore
750
751    # list-style only:
752
753    def get_level_style(self, level: int) -> Style | None:
754        if self.family != "list":
755            return None
756        level_styles = (
757            "(text:list-level-style-number"
758            "|text:list-level-style-bullet"
759            "|text:list-level-style-image)"
760        )
761        return self._filtered_element(level_styles, 0, level=level)  # type: ignore
762
763    def set_level_style(  # noqa: C901
764        self,
765        level: int,
766        num_format: str | None = None,
767        bullet_char: str | None = None,
768        url: str | None = None,
769        display_levels: int | None = None,
770        prefix: str | None = None,
771        suffix: str | None = None,
772        start_value: int | None = None,
773        style: str | None = None,
774        clone: Style | None = None,
775    ) -> Style | None:
776        """
777        Arguments:
778
779            level -- int
780
781            num_format (for number) -- int
782
783            bullet_char (for bullet) -- str
784
785            url (for image) -- str
786
787            display_levels -- int
788
789            prefix -- str
790
791            suffix -- str
792
793            start_value -- int
794
795            style -- str
796
797            clone -- List Style
798
799        Return:
800            level_style created
801        """
802        if self.family != "list":
803            return None
804        # Expected name
805        if num_format is not None:
806            level_style_name = "text:list-level-style-number"
807        elif bullet_char is not None:
808            level_style_name = "text:list-level-style-bullet"
809        elif url is not None:
810            level_style_name = "text:list-level-style-image"
811        elif clone is not None:
812            level_style_name = clone.tag
813        else:
814            raise ValueError("unknown level style type")
815        was_created = False
816        # Cloning or reusing an existing element
817        level_style: Style | None = None
818        if clone is not None:
819            level_style = clone.clone  # type: ignore
820            was_created = True
821        else:
822            level_style = self.get_level_style(level)
823            if level_style is None:
824                level_style = Element.from_tag(level_style_name)  # type: ignore
825                was_created = True
826        if level_style is None:
827            return None
828        # Transmute if the type changed
829        if level_style.tag != level_style_name:
830            print("Warn: different style", level_style_name, level_style.tag)
831            level_style.tag = level_style_name
832        # Set the level
833        level_style.set_attribute("text:level", str(level))
834        # Set the main attribute
835        if num_format is not None:
836            level_style.set_attribute("fo:num-format", num_format)
837        elif bullet_char is not None:
838            level_style.set_attribute("text:bullet-char", bullet_char)
839        elif url is not None:
840            level_style.set_attribute("xlink:href", url)
841        # Set attributes
842        if prefix:
843            level_style.set_attribute("style:num-prefix", prefix)
844        if suffix:
845            level_style.set_attribute("style:num-suffix", suffix)
846        if display_levels:
847            level_style.set_attribute("text:display-levels", str(display_levels))
848        if start_value:
849            level_style.set_attribute("text:start-value", str(start_value))
850        if style:
851            level_style.text_style = style  # type: ignore
852        # Commit the creation
853        if was_created:
854            self.append(level_style)
855        return level_style
856
857    # page-layout only:
858
859    def get_header_style(self) -> Element | None:
860        if self.family != "page-layout":
861            return None
862        return self.get_element("style:header-style")
863
864    def set_header_style(self, new_style: Style) -> None:
865        if self.family != "page-layout":
866            return
867        header_style = self.get_header_style()
868        if header_style is not None:
869            self.delete(header_style)
870        self.append(new_style)
871
872    def get_footer_style(self) -> Style | None:
873        if self.family != "page-layout":
874            return None
875        return self.get_element("style:footer-style")  # type: ignore
876
877    def set_footer_style(self, new_style: Style) -> None:
878        if self.family != "page-layout":
879            return
880        footer_style = self.get_footer_style()
881        if footer_style is not None:
882            self.delete(footer_style)
883        self.append(new_style)
884
885    # master-page only:
886
887    def _set_header_or_footer(
888        self,
889        text_or_element: str | Element | list[Element | str],
890        name: str = "header",
891        style: str = "Header",
892    ) -> None:
893        if name == "header":
894            header_or_footer = self.get_page_header()
895        else:
896            header_or_footer = self.get_page_footer()
897        if header_or_footer is None:
898            header_or_footer = Element.from_tag("style:" + name)
899            self.append(header_or_footer)
900        else:
901            header_or_footer.clear()
902        if (
903            isinstance(text_or_element, Element)
904            and text_or_element.tag == f"style:{name}"
905        ):
906            # Already a header or footer?
907            self.delete(header_or_footer)
908            self.append(text_or_element)
909            return
910        if isinstance(text_or_element, (Element, str)):
911            elem_list: list[Element | str] = [text_or_element]
912        else:
913            elem_list = text_or_element
914        for item in elem_list:
915            if isinstance(item, str):
916                paragraph = Element.from_tag("text:p")
917                paragraph.style = style  # type: ignore
918                header_or_footer.append(paragraph)
919            elif isinstance(item, Element):
920                header_or_footer.append(item)
921
922    def get_page_header(self) -> Element | None:
923        """Get the element that contains the header contents.
924
925        If None, no header was set.
926        """
927        if self.family != "master-page":
928            return None
929        return self.get_element("style:header")
930
931    def set_page_header(
932        self,
933        text_or_element: str | Element | list[Element | str],
934    ) -> None:
935        """Create or replace the header by the given content. It can already
936        be a complete header.
937
938        If you only want to update the existing header, get it and use the
939        API.
940
941        Arguments:
942
943            text_or_element -- str or Element or a list of them
944        """
945        if self.family != "master-page":
946            return None
947        self._set_header_or_footer(text_or_element)
948
949    def get_page_footer(self) -> Element | None:
950        """Get the element that contains the footer contents.
951
952        If None, no footer was set.
953        """
954        if self.family != "master-page":
955            return None
956        return self.get_element("style:footer")
957
958    def set_page_footer(
959        self,
960        text_or_element: str | Element | list[Element | str],
961    ) -> None:
962        """Create or replace the footer by the given content. It can already
963        be a complete footer.
964
965        If you only want to update the existing footer, get it and use the
966        API.
967
968        Arguments:
969
970            text_or_element -- str or Element or a list of them
971        """
972        if self.family != "master-page":
973            return None
974        self._set_header_or_footer(text_or_element, name="footer", style="Footer")
975
976    # font-face only:
977
978    def set_font(
979        self,
980        name: str,
981        family: str | None = None,
982        family_generic: str | None = None,
983        pitch: str = "variable",
984    ) -> None:
985        if self.family != "font-face":
986            return
987        self.name = name
988        if family is None:
989            family = name
990        self.svg_font_family = f'"{family}"'
991        if family_generic is not None:
992            self.font_family_generic = family_generic
993        self.font_pitch = pitch

Style class for all these tags:

'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...

Style( family: str | None = None, name: str | None = None, display_name: str | None = None, parent_style: str | None = None, area: str | None = None, color: str | tuple | None = None, background_color: str | tuple | None = None, italic: bool = False, bold: bool = False, master_page: str | None = None, page_layout: str | None = None, next_style: str | None = None, data_style: str | None = None, border: str | None = None, border_top: str | None = None, border_right: str | None = None, border_bottom: str | None = None, border_left: str | None = None, padding: str | None = None, padding_top: str | None = None, padding_bottom: str | None = None, padding_left: str | None = None, padding_right: str | None = None, shadow: str | None = None, height: str | None = None, use_optimal_height: bool = False, width: str | None = None, break_before: str | None = None, break_after: str | None = None, min_height: str | None = None, font_name: str | None = None, font_family: str | None = None, font_family_generic: str | None = None, font_pitch: str = 'variable', **kwargs: Any)
324    def __init__(  # noqa: C901
325        self,
326        family: str | None = None,
327        name: str | None = None,
328        display_name: str | None = None,
329        parent_style: str | None = None,
330        # Where properties apply
331        area: str | None = None,
332        # For family 'text':
333        color: str | tuple | None = None,
334        background_color: str | tuple | None = None,
335        italic: bool = False,
336        bold: bool = False,
337        # For family 'paragraph'
338        master_page: str | None = None,
339        # For family 'master-page'
340        page_layout: str | None = None,
341        next_style: str | None = None,
342        # For family 'table-cell'
343        data_style: str | None = None,  # unused
344        border: str | None = None,
345        border_top: str | None = None,
346        border_right: str | None = None,
347        border_bottom: str | None = None,
348        border_left: str | None = None,
349        padding: str | None = None,
350        padding_top: str | None = None,
351        padding_bottom: str | None = None,
352        padding_left: str | None = None,
353        padding_right: str | None = None,
354        shadow: str | None = None,
355        # For family 'table-row'
356        height: str | None = None,
357        use_optimal_height: bool = False,
358        # For family 'table-column'
359        width: str | None = None,
360        break_before: str | None = None,
361        break_after: str | None = None,
362        # For family 'graphic'
363        min_height: str | None = None,
364        # For family 'font-face'
365        font_name: str | None = None,
366        font_family: str | None = None,
367        font_family_generic: str | None = None,
368        font_pitch: str = "variable",
369        # Every other property
370        **kwargs: Any,
371    ) -> None:
372        """Create a style of the given family. The name is not mandatory at this
373        point but will become required when inserting in a document as a common
374        style.
375
376        The display name is the name the user sees in an office application.
377
378        The parent_style is the name of the style this style will inherit from.
379
380        To set properties, pass them as keyword arguments. The area properties
381        apply to is optional and defaults to the family.
382
383        Arguments:
384
385            family -- 'paragraph', 'text', 'section', 'table', 'table-column',
386                      'table-row', 'table-cell', 'table-page', 'chart',
387                      'drawing-page', 'graphic', 'presentation',
388                      'control', 'ruby', 'list', 'number', 'page-layout'
389                      'font-face', or 'master-page'
390
391            name -- str
392
393            display_name -- str
394
395            parent_style -- str
396
397            area -- str
398
399        'text' Properties:
400
401            italic -- bool
402
403            bold -- bool
404
405        'paragraph' Properties:
406
407            master_page -- str
408
409        'master-page' Properties:
410
411            page_layout -- str
412
413            next_style -- str
414
415        'table-cell' Properties:
416
417            border, border_top, border_right, border_bottom, border_left -- str,
418            e.g. "0.002cm solid #000000" or 'none'
419
420            padding, padding_top, padding_right, padding_bottom, padding_left -- str,
421            e.g. "0.002cm" or 'none'
422
423            shadow -- str, e.g. "#808080 0.176cm 0.176cm"
424
425        'table-row' Properties:
426
427            height -- str, e.g. '5cm'
428
429            use_optimal_height -- bool
430
431        'table-column' Properties:
432
433            width -- str, e.g. '5cm'
434
435            break_before -- 'page', 'column' or 'auto'
436
437            break_after -- 'page', 'column' or 'auto'
438        """
439        self._family: str | None = None
440        tag_or_elem = kwargs.get("tag_or_elem", None)
441        if tag_or_elem is None:
442            family = to_str(family)
443            if family not in FAMILY_MAPPING:
444                raise ValueError("Unknown family value: %s" % family)
445            kwargs["tag"] = FAMILY_MAPPING[family]
446        super().__init__(**kwargs)
447        if self._do_init and family not in SUBCLASSED_STYLES:
448            kwargs.pop("tag", None)
449            kwargs.pop("tag_or_elem", None)
450            self.family = family  # relevant test made by property
451            # Common attributes
452            if name:
453                self.name = name
454            if display_name:
455                self.display_name = display_name
456            if parent_style:
457                self.parent_style = parent_style
458            # Paragraph
459            if family == "paragraph":
460                if master_page:
461                    self.master_page = master_page
462            # Master Page
463            elif family == "master-page":
464                if page_layout:
465                    self.page_layout = page_layout
466                if next_style:
467                    self.next_style = next_style
468            # Font face
469            elif family == "font-face":
470                if not font_name:
471                    raise ValueError("A font_name is required for 'font-face' style")
472                self.set_font(
473                    font_name,
474                    family=font_family,
475                    family_generic=font_family_generic,
476                    pitch=font_pitch,
477                )
478            # Properties
479            if area is None:
480                area = family
481            area = to_str(area)
482            # Text
483            if area == "text":
484                if color:
485                    kwargs["fo:color"] = color
486                if background_color:
487                    kwargs["fo:background-color"] = background_color
488                if italic:
489                    kwargs["fo:font-style"] = "italic"
490                    kwargs["style:font-style-asian"] = "italic"
491                    kwargs["style:font-style-complex"] = "italic"
492                if bold:
493                    kwargs["fo:font-weight"] = "bold"
494                    kwargs["style:font-weight-asian"] = "bold"
495                    kwargs["style:font-weight-complex"] = "bold"
496            # Table cell
497            elif area == "table-cell":
498                if border:
499                    kwargs["fo:border"] = border
500                elif border_top or border_right or border_bottom or border_left:
501                    kwargs["fo:border-top"] = border_top or "none"
502                    kwargs["fo:border-right"] = border_right or "none"
503                    kwargs["fo:border-bottom"] = border_bottom or "none"
504                    kwargs["fo:border-left"] = border_left or "none"
505                else:  # no border_top, ... neither border are defined
506                    pass  # left untouched
507                if padding:
508                    kwargs["fo:padding"] = padding
509                elif padding_top or padding_right or padding_bottom or padding_left:
510                    kwargs["fo:padding-top"] = padding_top or "none"
511                    kwargs["fo:padding-right"] = padding_right or "none"
512                    kwargs["fo:padding-bottom"] = padding_bottom or "none"
513                    kwargs["fo:padding-left"] = padding_left or "none"
514                else:  # no border_top, ... neither border are defined
515                    pass  # left untouched
516                if shadow:
517                    kwargs["style:shadow"] = shadow
518                if background_color:
519                    kwargs["fo:background-color"] = background_color
520            # Table row
521            elif area == "table-row":
522                if height:
523                    kwargs["style:row-height"] = height
524                if use_optimal_height:
525                    kwargs["style:use-optimal-row-height"] = Boolean.encode(
526                        use_optimal_height
527                    )
528                if background_color:
529                    kwargs["fo:background-color"] = background_color
530            # Table column
531            elif area == "table-column":
532                if width:
533                    kwargs["style:column-width"] = width
534                if break_before:
535                    kwargs["fo:break-before"] = break_before
536                if break_after:
537                    kwargs["fo:break-after"] = break_after
538            # Graphic
539            elif area == "graphic":
540                if min_height:
541                    kwargs["fo:min-height"] = min_height
542            # Every other properties
543            if kwargs:
544                self.set_properties(kwargs, area=area)

Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.

The display name is the name the user sees in an office application.

The parent_style is the name of the style this style will inherit from.

To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.

Arguments:

family -- 'paragraph', 'text', 'section', 'table', 'table-column',
          'table-row', 'table-cell', 'table-page', 'chart',
          'drawing-page', 'graphic', 'presentation',
          'control', 'ruby', 'list', 'number', 'page-layout'
          'font-face', or 'master-page'

name -- str

display_name -- str

parent_style -- str

area -- str

'text' Properties:

italic -- bool

bold -- bool

'paragraph' Properties:

master_page -- str

'master-page' Properties:

page_layout -- str

next_style -- str

'table-cell' Properties:

border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'

padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

'table-row' Properties:

height -- str, e.g. '5cm'

use_optimal_height -- bool

'table-column' Properties:

width -- str, e.g. '5cm'

break_before -- 'page', 'column' or 'auto'

break_after -- 'page', 'column' or 'auto'
family: str | None
546    @property
547    def family(self) -> str | None:
548        if self._family is None:
549            self._family = FALSE_FAMILY_MAP_REVERSE.get(
550                self.tag, self.get_attribute_string("style:family")
551            )
552        return self._family
def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
560    def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
561        """Get the mapping of all properties of this style. By default the
562        properties of the same family, e.g. a paragraph style and its
563        paragraph properties. Specify the area to get the text properties of
564        a paragraph style for example.
565
566        Arguments:
567
568            area -- str
569
570        Return: dict
571        """
572        if area is None:
573            area = self.family
574        element = self.get_element(f"style:{area}-properties")
575        if element is None:
576            return None
577        properties: dict[str, str | dict] = element.attributes  # type: ignore
578        # Nested properties are nested dictionaries
579        for child in element.children:
580            properties[child.tag] = child.attributes
581        return properties

Get the mapping of all properties of this style. By default the properties of the same family, e.g. a paragraph style and its paragraph properties. Specify the area to get the text properties of a paragraph style for example.

Arguments:

area -- str

Return: dict

def set_properties( self, properties: dict[str, str | dict] | None = None, style: Style | None = None, area: str | None = None, **kwargs: Any) -> None:
583    def set_properties(  # noqa: C901
584        self,
585        properties: dict[str, str | dict] | None = None,
586        style: Style | None = None,
587        area: str | None = None,
588        **kwargs: Any,
589    ) -> None:
590        """Set the properties of the "area" type of this style. Properties
591        are given either as a dict or as named arguments (or both). The area
592        is identical to the style family by default. If the properties
593        element is missing, it is created.
594
595        Instead of properties, you can pass a style with properties of the
596        same area. These will be copied.
597
598        Arguments:
599
600            properties -- dict
601
602            style -- Style
603
604            area -- 'paragraph', 'text'...
605        """
606        if properties is None:
607            properties = {}
608        if area is None:
609            if isinstance(self.family, bool):
610                area = None
611            else:
612                area = self.family
613        element = self.get_element(f"style:{area}-properties")
614        if element is None:
615            element = Element.from_tag(f"style:{area}-properties")
616            self.append(element)
617        if properties or kwargs:
618            properties = _expand_properties_dict(_merge_dicts(properties, kwargs))
619        elif style is not None:
620            properties = style.get_properties(area=area)
621            if properties is None:
622                return
623        if properties is None:
624            return
625        for key, value in properties.items():
626            if value is None:
627                element.del_attribute(key)
628            elif isinstance(value, (str, bool, tuple)):
629                element.set_attribute(key, value)
630            else:
631                pass

Set the properties of the "area" type of this style. Properties are given either as a dict or as named arguments (or both). The area is identical to the style family by default. If the properties element is missing, it is created.

Instead of properties, you can pass a style with properties of the same area. These will be copied.

Arguments:

properties -- dict

style -- Style

area -- 'paragraph', 'text'...
def del_properties( self, properties: list[str] | None = None, area: str | None = None) -> None:
633    def del_properties(
634        self,
635        properties: list[str] | None = None,
636        area: str | None = None,
637    ) -> None:
638        """Delete the given properties, either by list argument or
639        positional argument (or both). Remove only from the given area,
640        identical to the style family by default.
641
642        Arguments:
643
644            properties -- list
645
646            area -- str
647        """
648        if properties is None:
649            properties = []
650        if area is None:
651            area = self.family
652        element = self.get_element(f"style:{area}-properties")
653        if element is None:
654            raise ValueError(
655                f"properties element is inexistent for: style:{area}-properties"
656            )
657        for key in _expand_properties_list(properties):
658            element.del_attribute(key)

Delete the given properties, either by list argument or positional argument (or both). Remove only from the given area, identical to the style family by default.

Arguments:

properties -- list

area -- str
def set_background( self, color: str | None = None, url: str | None = None, position: str | None = 'center', repeat: str | None = None, opacity: str | None = None, filter: str | None = None) -> None:
660    def set_background(  # noqa: C901
661        self,
662        color: str | None = None,
663        url: str | None = None,
664        position: str | None = "center",
665        repeat: str | None = None,
666        opacity: str | None = None,
667        filter: str | None = None,  # noqa: A002
668    ) -> None:
669        """Set the background color of a text style, or the background color
670        or image of a paragraph style or page layout.
671
672        With no argument, remove any existing background.
673
674        The position is one or two of 'center', 'left', 'right', 'top' or
675        'bottom'.
676
677        The repeat is 'no-repeat', 'repeat' or 'stretch'.
678
679        The opacity is a percentage integer (not a string with the '%s' sign)
680
681        The filter is an application-specific filter name defined elsewhere.
682
683        Though this method is defined on the base style class, it will raise
684        an error if the style type is not compatible.
685
686        Arguments:
687
688            color -- '#rrggbb'
689
690            url -- str
691
692            position -- str
693
694            repeat -- str
695
696            opacity -- int
697
698            filter -- str
699        """
700        family = self.family
701        if family not in {
702            "text",
703            "paragraph",
704            "page-layout",
705            "section",
706            "table",
707            "table-row",
708            "table-cell",
709            "graphic",
710        }:
711            raise TypeError("No background support for this family")
712        if url is not None and family == "text":
713            raise TypeError("No background image for text styles")
714        properties = self.get_element(f"style:{family}-properties")
715        bg_image: BackgroundImage | None = None
716        if properties is not None:
717            bg_image = properties.get_element("style:background-image")  # type:ignore
718        # Erasing
719        if color is None and url is None:
720            if properties is None:
721                return
722            properties.del_attribute("fo:background-color")
723            if bg_image is not None:
724                properties.delete(bg_image)
725            return
726        # Add the properties if necessary
727        if properties is None:
728            properties = Element.from_tag(f"style:{family}-properties")
729            self.append(properties)
730        # Add the color...
731        if color:
732            properties.set_attribute("fo:background-color", color)
733            if bg_image is not None:
734                properties.delete(bg_image)
735        # ... or the background
736        elif url:
737            properties.set_attribute("fo:background-color", "transparent")
738            if bg_image is None:
739                bg_image = Element.from_tag("style:background-image")  # type:ignore
740                properties.append(bg_image)  # type:ignore
741            bg_image.url = url  # type:ignore
742            if position:
743                bg_image.position = position  # type:ignore
744            if repeat:
745                bg_image.repeat = repeat  # type:ignore
746            if opacity:
747                bg_image.opacity = opacity  # type:ignore
748            if filter:
749                bg_image.filter = filter  # type:ignore

Set the background color of a text style, or the background color or image of a paragraph style or page layout.

With no argument, remove any existing background.

The position is one or two of 'center', 'left', 'right', 'top' or 'bottom'.

The repeat is 'no-repeat', 'repeat' or 'stretch'.

The opacity is a percentage integer (not a string with the '%s' sign)

The filter is an application-specific filter name defined elsewhere.

Though this method is defined on the base style class, it will raise an error if the style type is not compatible.

Arguments:

color -- '#rrggbb'

url -- str

position -- str

repeat -- str

opacity -- int

filter -- str
def get_level_style(self, level: int) -> Style | None:
753    def get_level_style(self, level: int) -> Style | None:
754        if self.family != "list":
755            return None
756        level_styles = (
757            "(text:list-level-style-number"
758            "|text:list-level-style-bullet"
759            "|text:list-level-style-image)"
760        )
761        return self._filtered_element(level_styles, 0, level=level)  # type: ignore
def set_level_style( self, level: int, num_format: str | None = None, bullet_char: str | None = None, url: str | None = None, display_levels: int | None = None, prefix: str | None = None, suffix: str | None = None, start_value: int | None = None, style: str | None = None, clone: Style | None = None) -> Style | None:
763    def set_level_style(  # noqa: C901
764        self,
765        level: int,
766        num_format: str | None = None,
767        bullet_char: str | None = None,
768        url: str | None = None,
769        display_levels: int | None = None,
770        prefix: str | None = None,
771        suffix: str | None = None,
772        start_value: int | None = None,
773        style: str | None = None,
774        clone: Style | None = None,
775    ) -> Style | None:
776        """
777        Arguments:
778
779            level -- int
780
781            num_format (for number) -- int
782
783            bullet_char (for bullet) -- str
784
785            url (for image) -- str
786
787            display_levels -- int
788
789            prefix -- str
790
791            suffix -- str
792
793            start_value -- int
794
795            style -- str
796
797            clone -- List Style
798
799        Return:
800            level_style created
801        """
802        if self.family != "list":
803            return None
804        # Expected name
805        if num_format is not None:
806            level_style_name = "text:list-level-style-number"
807        elif bullet_char is not None:
808            level_style_name = "text:list-level-style-bullet"
809        elif url is not None:
810            level_style_name = "text:list-level-style-image"
811        elif clone is not None:
812            level_style_name = clone.tag
813        else:
814            raise ValueError("unknown level style type")
815        was_created = False
816        # Cloning or reusing an existing element
817        level_style: Style | None = None
818        if clone is not None:
819            level_style = clone.clone  # type: ignore
820            was_created = True
821        else:
822            level_style = self.get_level_style(level)
823            if level_style is None:
824                level_style = Element.from_tag(level_style_name)  # type: ignore
825                was_created = True
826        if level_style is None:
827            return None
828        # Transmute if the type changed
829        if level_style.tag != level_style_name:
830            print("Warn: different style", level_style_name, level_style.tag)
831            level_style.tag = level_style_name
832        # Set the level
833        level_style.set_attribute("text:level", str(level))
834        # Set the main attribute
835        if num_format is not None:
836            level_style.set_attribute("fo:num-format", num_format)
837        elif bullet_char is not None:
838            level_style.set_attribute("text:bullet-char", bullet_char)
839        elif url is not None:
840            level_style.set_attribute("xlink:href", url)
841        # Set attributes
842        if prefix:
843            level_style.set_attribute("style:num-prefix", prefix)
844        if suffix:
845            level_style.set_attribute("style:num-suffix", suffix)
846        if display_levels:
847            level_style.set_attribute("text:display-levels", str(display_levels))
848        if start_value:
849            level_style.set_attribute("text:start-value", str(start_value))
850        if style:
851            level_style.text_style = style  # type: ignore
852        # Commit the creation
853        if was_created:
854            self.append(level_style)
855        return level_style

Arguments:

level -- int

num_format (for number) -- int

bullet_char (for bullet) -- str

url (for image) -- str

display_levels -- int

prefix -- str

suffix -- str

start_value -- int

style -- str

clone -- List Style

Return: level_style created

def get_header_style(self) -> Element | None:
859    def get_header_style(self) -> Element | None:
860        if self.family != "page-layout":
861            return None
862        return self.get_element("style:header-style")
def set_header_style(self, new_style: Style) -> None:
864    def set_header_style(self, new_style: Style) -> None:
865        if self.family != "page-layout":
866            return
867        header_style = self.get_header_style()
868        if header_style is not None:
869            self.delete(header_style)
870        self.append(new_style)
def get_page_header(self) -> Element | None:
922    def get_page_header(self) -> Element | None:
923        """Get the element that contains the header contents.
924
925        If None, no header was set.
926        """
927        if self.family != "master-page":
928            return None
929        return self.get_element("style:header")

Get the element that contains the header contents.

If None, no header was set.

def set_page_header( self, text_or_element: str | Element | list[Element | str]) -> None:
931    def set_page_header(
932        self,
933        text_or_element: str | Element | list[Element | str],
934    ) -> None:
935        """Create or replace the header by the given content. It can already
936        be a complete header.
937
938        If you only want to update the existing header, get it and use the
939        API.
940
941        Arguments:
942
943            text_or_element -- str or Element or a list of them
944        """
945        if self.family != "master-page":
946            return None
947        self._set_header_or_footer(text_or_element)

Create or replace the header by the given content. It can already be a complete header.

If you only want to update the existing header, get it and use the API.

Arguments:

text_or_element -- str or Element or a list of them
def set_font( self, name: str, family: str | None = None, family_generic: str | None = None, pitch: str = 'variable') -> None:
978    def set_font(
979        self,
980        name: str,
981        family: str | None = None,
982        family_generic: str | None = None,
983        pitch: str = "variable",
984    ) -> None:
985        if self.family != "font-face":
986            return
987        self.name = name
988        if family is None:
989            family = name
990        self.svg_font_family = f'"{family}"'
991        if family_generic is not None:
992            self.font_family_generic = family_generic
993        self.font_pitch = pitch
page_layout: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
next_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
parent_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
display_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
svg_font_family: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_family_generic: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_pitch: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
text_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
master_page: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_text: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Styles(odfdo.XmlPart):
 58class Styles(XmlPart):
 59    def _get_style_contexts(
 60        self, family: str, automatic: bool = False
 61    ) -> list[Element]:
 62        if automatic:
 63            return [self.get_element("//office:automatic-styles")]
 64        if not family:
 65            # All possibilities
 66            return [
 67                self.get_element("//office:automatic-styles"),
 68                self.get_element("//office:styles"),
 69                self.get_element("//office:master-styles"),
 70                self.get_element("//office:font-face-decls"),
 71            ]
 72        queries = CONTEXT_MAPPING.get(family)
 73        if queries is None:
 74            raise ValueError(f"unknown family: {family}")
 75        # print('q:', queries)
 76        return [self.get_element(query) for query in queries]
 77
 78    def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]:
 79        """Return the list of styles in the Content part, optionally limited
 80        to the given family, optionaly limited to automatic styles.
 81
 82        Arguments:
 83
 84            family -- str
 85
 86            automatic -- bool
 87
 88        Return: list of Style
 89        """
 90        result = []
 91        for context in self._get_style_contexts(family, automatic=automatic):
 92            if context is None:
 93                continue
 94            # print('-ctx----', automatic)
 95            # print(context.tag)
 96            # print(context.__class__)
 97            # print(context.serialize())
 98            result.extend(context.get_styles(family=family))
 99        return result
100
101    def get_style(
102        self,
103        family: str,
104        name_or_element: str | Style | None = None,
105        display_name: str | None = None,
106    ) -> Style | None:
107        """Return the style uniquely identified by the name/family pair. If
108        the argument is already a style object, it will return it.
109
110        If the name is None, the default style is fetched.
111
112        If the name is not the internal name but the name you gave in the
113        desktop application, use display_name instead.
114
115        Arguments:
116
117            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
118                      'number', 'page-layout', 'master-page'
119
120            name_or_element -- str, odf_style or None
121
122            display_name -- str or None
123
124        Return: odf_style or None if not found
125        """
126        for context in self._get_style_contexts(family):
127            if context is None:
128                continue
129            style = context.get_style(
130                family,
131                name_or_element=name_or_element,
132                display_name=display_name,
133            )
134            if style is not None:
135                return style  # type: ignore
136        return None
137
138    def get_master_pages(self) -> list[Element]:
139        query = make_xpath_query("descendant::style:master-page")
140        return self.get_elements(query)  # type:ignore
141
142    def get_master_page(self, position: int = 0) -> Element | None:
143        results = self.get_master_pages()
144        try:
145            return results[position]
146        except IndexError:
147            return None

Representation of an XML part.

Abstraction of the XML library behind.

def get_styles( self, family: str = '', automatic: bool = False) -> list[Element]:
78    def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]:
79        """Return the list of styles in the Content part, optionally limited
80        to the given family, optionaly limited to automatic styles.
81
82        Arguments:
83
84            family -- str
85
86            automatic -- bool
87
88        Return: list of Style
89        """
90        result = []
91        for context in self._get_style_contexts(family, automatic=automatic):
92            if context is None:
93                continue
94            # print('-ctx----', automatic)
95            # print(context.tag)
96            # print(context.__class__)
97            # print(context.serialize())
98            result.extend(context.get_styles(family=family))
99        return result

Return the list of styles in the Content part, optionally limited to the given family, optionaly limited to automatic styles.

Arguments:

family -- str

automatic -- bool

Return: list of Style

def get_style( self, family: str, name_or_element: str | Style | None = None, display_name: str | None = None) -> Style | None:
101    def get_style(
102        self,
103        family: str,
104        name_or_element: str | Style | None = None,
105        display_name: str | None = None,
106    ) -> Style | None:
107        """Return the style uniquely identified by the name/family pair. If
108        the argument is already a style object, it will return it.
109
110        If the name is None, the default style is fetched.
111
112        If the name is not the internal name but the name you gave in the
113        desktop application, use display_name instead.
114
115        Arguments:
116
117            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
118                      'number', 'page-layout', 'master-page'
119
120            name_or_element -- str, odf_style or None
121
122            display_name -- str or None
123
124        Return: odf_style or None if not found
125        """
126        for context in self._get_style_contexts(family):
127            if context is None:
128                continue
129            style = context.get_style(
130                family,
131                name_or_element=name_or_element,
132                display_name=display_name,
133            )
134            if style is not None:
135                return style  # type: ignore
136        return None

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text',  'graphic', 'table', 'list',
          'number', 'page-layout', 'master-page'

name_or_element -- str, odf_style or None

display_name -- str or None

Return: odf_style or None if not found

def get_master_pages(self) -> list[Element]:
138    def get_master_pages(self) -> list[Element]:
139        query = make_xpath_query("descendant::style:master-page")
140        return self.get_elements(query)  # type:ignore
def get_master_page(self, position: int = 0) -> Element | None:
142    def get_master_page(self, position: int = 0) -> Element | None:
143        results = self.get_master_pages()
144        try:
145            return results[position]
146        except IndexError:
147            return None
class TOC(odfdo.Element):
168class TOC(Element):
169    """Table of content.
170    The "text:table-of-content" element represents a table of contents for a
171    document. The items that can be listed in a table of contents are:
172      - Headings (as defined by the outline structure of the document), up to
173        a selected level.
174      - Table of contents index marks.
175      - Paragraphs formatted with specified paragraph styles.
176
177
178    Implementation:
179    Default parameters are what most people use: protected from manual
180    modifications and not limited in title levels.
181
182    The name is mandatory and derived automatically from the title if not
183    given. Provide one in case of a conflict with other TOCs in the same
184    document.
185
186    The "text:table-of-content" element has the following attributes:
187    text:name, text:protected, text:protection-key,
188    text:protection-key-digest-algorithm, text:style-name and xml:id.
189
190    Arguments:
191
192        title -- str
193
194        name -- str
195
196        protected -- bool
197
198        outline_level -- int
199
200        style -- str
201
202        title_style -- str
203
204        entry_style -- str
205    """
206
207    _tag = "text:table-of-content"
208    _properties = (
209        PropDef("name", "text:name"),
210        PropDef("style", "text:style-name"),
211        PropDef("xml_id", "xml:id"),
212        PropDef("protected", "text:protected"),
213        PropDef("protection_key", "text:protection-key"),
214        PropDef(
215            "protection_key_digest_algorithm", "text:protection-key-digest-algorithm"
216        ),
217    )
218
219    def __init__(
220        self,
221        title: str = "Table of Contents",
222        name: str | None = None,
223        protected: bool = True,
224        outline_level: int = 0,
225        style: str | None = None,
226        title_style: str = "Contents_20_Heading",
227        entry_style: str = "Contents_20_%d",
228        **kwargs: Any,
229    ) -> None:
230        super().__init__(**kwargs)
231        if self._do_init:
232            if style:
233                self.style = style
234            if protected:
235                self.protected = protected
236            if name is None:
237                self.name = f"{title}1"
238            # Create the source template
239            toc_source = self.create_toc_source(
240                title, outline_level, title_style, entry_style
241            )
242            self.append(toc_source)
243            # Create the index body automatically with the index title
244            if title:
245                # This style is in the template document
246                self.set_toc_title(title, text_style=title_style)
247
248    @staticmethod
249    def create_toc_source(
250        title: str,
251        outline_level: int,
252        title_style: str,
253        entry_style: str,
254    ) -> Element:
255        toc_source = Element.from_tag("text:table-of-content-source")
256        toc_source.set_attribute("text:outline-level", str(outline_level))
257        if title:
258            title_template = IndexTitleTemplate()
259            if title_style:
260                # This style is in the template document
261                title_template.style = title_style
262            title_template.text = title
263            toc_source.append(title_template)
264        for level in range(1, 11):
265            template = TocEntryTemplate(outline_level=level)
266            if entry_style:
267                template.style = entry_style % level
268            toc_source.append(template)
269        return toc_source
270
271    def __str__(self) -> str:
272        return self.get_formatted_text()
273
274    def get_formatted_text(self, context: dict | None = None) -> str:
275        index_body = self.get_element("text:index-body")
276
277        if index_body is None:
278            return ""
279        if context is None:
280            context = {}
281        if context.get("rst_mode"):
282            return "\n.. contents::\n\n"
283
284        result = []
285        for element in index_body.children:
286            if element.tag == "text:index-title":
287                for child_element in element.children:
288                    result.append(child_element.get_formatted_text(context).strip())
289            else:
290                result.append(element.get_formatted_text(context).strip())
291        return "\n".join(result)
292
293    @property
294    def outline_level(self) -> int | None:
295        source = self.get_element("text:table-of-content-source")
296        if source is None:
297            return None
298        return source.get_attribute_integer("text:outline-level")
299
300    @outline_level.setter
301    def outline_level(self, level: int) -> None:
302        source = self.get_element("text:table-of-content-source")
303        if source is None:
304            source = Element.from_tag("text:table-of-content-source")
305            self.insert(source, FIRST_CHILD)
306        source.set_attribute("text:outline-level", str(level))
307
308    @property
309    def body(self) -> Element | None:
310        return self.get_element("text:index-body")
311
312    @body.setter
313    def body(self, body: Element | None = None) -> Element | None:
314        old_body = self.body
315        if old_body is not None:
316            self.delete(old_body)
317        if body is None:
318            body = Element.from_tag("text:index-body")
319        self.append(body)
320        return body
321
322    def get_title(self) -> str:
323        index_body = self.body
324        if index_body is None:
325            return ""
326        index_title = index_body.get_element(IndexTitle._tag)
327        if index_title is None:
328            return ""
329        return index_title.text_content
330
331    def set_toc_title(
332        self,
333        title: str,
334        style: str | None = None,
335        text_style: str | None = None,
336    ) -> None:
337        index_body = self.body
338        if index_body is None:
339            self.body = None
340            index_body = self.body
341        index_title = index_body.get_element(IndexTitle._tag)  # type: ignore
342        if index_title is None:
343            name = f"{self.name}_Head"
344            index_title = IndexTitle(
345                name=name, style=style, title_text=title, text_style=text_style
346            )
347            index_body.append(index_title)  # type: ignore
348        else:
349            if style:
350                index_title.style = style  # type: ignore
351            paragraph = index_title.get_paragraph()
352            if paragraph is None:
353                paragraph = Paragraph()
354                index_title.append(paragraph)
355            if text_style:
356                paragraph.style = text_style  # type: ignore
357            paragraph.text = title
358
359    @staticmethod
360    def _header_numbering(level_indexes: dict[int, int], level: int) -> str:
361        """Return the header hierarchical number (like "1.2.3.")."""
362        numbers: list[int] = []
363        # before header level
364        for idx in range(1, level):
365            numbers.append(level_indexes.setdefault(idx, 1))
366        # header level
367        index = level_indexes.get(level, 0) + 1
368        level_indexes[level] = index
369        numbers.append(index)
370        # after header level
371        idx = level + 1
372        while idx in level_indexes:
373            del level_indexes[idx]
374            idx += 1
375        return ".".join(str(x) for x in numbers) + "."
376
377    def fill(  # noqa: C901
378        self,
379        document: Document | None = None,
380        use_default_styles: bool = True,
381    ) -> None:
382        """Fill the TOC with the titles found in the document. A TOC is not
383        contextual so it will catch all titles before and after its insertion.
384        If the TOC is not attached to a document, attach it beforehand or
385        provide one as argument.
386
387        For having a pretty TOC, let use_default_styles by default.
388
389        Arguments:
390
391            document -- Document
392
393            use_default_styles -- bool
394        """
395        # Find the body
396        if document is not None:
397            body: Element | None = document.body
398        else:
399            body = self.document_body
400        if body is None:
401            raise ValueError("The TOC must be related to a document somehow")
402
403        # Save the title
404        index_body = self.body
405        title = index_body.get_element("text:index-title")  # type: ignore
406
407        # Clean the old index-body
408        self.body = None
409        index_body = self.body
410
411        # Restore the title
412        if title and str(title):
413            index_body.insert(title, position=0)  # type: ignore
414
415        # Insert default TOC style
416        if use_default_styles:
417            automatic_styles = body.get_element("//office:automatic-styles")
418            if isinstance(automatic_styles, Element):
419                for level in range(1, 11):
420                    if (
421                        automatic_styles.get_style(
422                            "paragraph", _toc_entry_style_name(level)
423                        )
424                        is None
425                    ):
426                        level_style = default_toc_level_style(level)
427                        automatic_styles.append(level_style)
428
429        # Auto-fill the index
430        outline_level = self.outline_level or 10
431        level_indexes: dict[int, int] = {}
432        for header in body.get_headers():
433            level = header.get_attribute_integer("text:outline-level") or 0
434            if level is None or level > outline_level:
435                continue
436            number_str = self._header_numbering(level_indexes, level)
437            # Make the title with "1.2.3. Title" format
438            paragraph = Paragraph(f"{number_str} {header}")
439            if use_default_styles:
440                paragraph.style = _toc_entry_style_name(level)
441            index_body.append(paragraph)  # type: ignore

Table of content. The "text:table-of-content" element represents a table of contents for a document. The items that can be listed in a table of contents are:

  • Headings (as defined by the outline structure of the document), up to a selected level.
  • Table of contents index marks.
  • Paragraphs formatted with specified paragraph styles.

Implementation: Default parameters are what most people use: protected from manual modifications and not limited in title levels.

The name is mandatory and derived automatically from the title if not given. Provide one in case of a conflict with other TOCs in the same document.

The "text:table-of-content" element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name and xml:id.

Arguments:

title -- str

name -- str

protected -- bool

outline_level -- int

style -- str

title_style -- str

entry_style -- str
TOC( title: str = 'Table of Contents', name: str | None = None, protected: bool = True, outline_level: int = 0, style: str | None = None, title_style: str = 'Contents_20_Heading', entry_style: str = 'Contents_20_%d', **kwargs: Any)
219    def __init__(
220        self,
221        title: str = "Table of Contents",
222        name: str | None = None,
223        protected: bool = True,
224        outline_level: int = 0,
225        style: str | None = None,
226        title_style: str = "Contents_20_Heading",
227        entry_style: str = "Contents_20_%d",
228        **kwargs: Any,
229    ) -> None:
230        super().__init__(**kwargs)
231        if self._do_init:
232            if style:
233                self.style = style
234            if protected:
235                self.protected = protected
236            if name is None:
237                self.name = f"{title}1"
238            # Create the source template
239            toc_source = self.create_toc_source(
240                title, outline_level, title_style, entry_style
241            )
242            self.append(toc_source)
243            # Create the index body automatically with the index title
244            if title:
245                # This style is in the template document
246                self.set_toc_title(title, text_style=title_style)
@staticmethod
def create_toc_source( title: str, outline_level: int, title_style: str, entry_style: str) -> Element:
248    @staticmethod
249    def create_toc_source(
250        title: str,
251        outline_level: int,
252        title_style: str,
253        entry_style: str,
254    ) -> Element:
255        toc_source = Element.from_tag("text:table-of-content-source")
256        toc_source.set_attribute("text:outline-level", str(outline_level))
257        if title:
258            title_template = IndexTitleTemplate()
259            if title_style:
260                # This style is in the template document
261                title_template.style = title_style
262            title_template.text = title
263            toc_source.append(title_template)
264        for level in range(1, 11):
265            template = TocEntryTemplate(outline_level=level)
266            if entry_style:
267                template.style = entry_style % level
268            toc_source.append(template)
269        return toc_source
def get_formatted_text(self, context: dict | None = None) -> str:
274    def get_formatted_text(self, context: dict | None = None) -> str:
275        index_body = self.get_element("text:index-body")
276
277        if index_body is None:
278            return ""
279        if context is None:
280            context = {}
281        if context.get("rst_mode"):
282            return "\n.. contents::\n\n"
283
284        result = []
285        for element in index_body.children:
286            if element.tag == "text:index-title":
287                for child_element in element.children:
288                    result.append(child_element.get_formatted_text(context).strip())
289            else:
290                result.append(element.get_formatted_text(context).strip())
291        return "\n".join(result)

This function should return a beautiful version of the text.

outline_level: int | None
293    @property
294    def outline_level(self) -> int | None:
295        source = self.get_element("text:table-of-content-source")
296        if source is None:
297            return None
298        return source.get_attribute_integer("text:outline-level")
body: Element | None
308    @property
309    def body(self) -> Element | None:
310        return self.get_element("text:index-body")
def get_title(self) -> str:
322    def get_title(self) -> str:
323        index_body = self.body
324        if index_body is None:
325            return ""
326        index_title = index_body.get_element(IndexTitle._tag)
327        if index_title is None:
328            return ""
329        return index_title.text_content
def set_toc_title( self, title: str, style: str | None = None, text_style: str | None = None) -> None:
331    def set_toc_title(
332        self,
333        title: str,
334        style: str | None = None,
335        text_style: str | None = None,
336    ) -> None:
337        index_body = self.body
338        if index_body is None:
339            self.body = None
340            index_body = self.body
341        index_title = index_body.get_element(IndexTitle._tag)  # type: ignore
342        if index_title is None:
343            name = f"{self.name}_Head"
344            index_title = IndexTitle(
345                name=name, style=style, title_text=title, text_style=text_style
346            )
347            index_body.append(index_title)  # type: ignore
348        else:
349            if style:
350                index_title.style = style  # type: ignore
351            paragraph = index_title.get_paragraph()
352            if paragraph is None:
353                paragraph = Paragraph()
354                index_title.append(paragraph)
355            if text_style:
356                paragraph.style = text_style  # type: ignore
357            paragraph.text = title
def fill( self, document: Document | None = None, use_default_styles: bool = True) -> None:
377    def fill(  # noqa: C901
378        self,
379        document: Document | None = None,
380        use_default_styles: bool = True,
381    ) -> None:
382        """Fill the TOC with the titles found in the document. A TOC is not
383        contextual so it will catch all titles before and after its insertion.
384        If the TOC is not attached to a document, attach it beforehand or
385        provide one as argument.
386
387        For having a pretty TOC, let use_default_styles by default.
388
389        Arguments:
390
391            document -- Document
392
393            use_default_styles -- bool
394        """
395        # Find the body
396        if document is not None:
397            body: Element | None = document.body
398        else:
399            body = self.document_body
400        if body is None:
401            raise ValueError("The TOC must be related to a document somehow")
402
403        # Save the title
404        index_body = self.body
405        title = index_body.get_element("text:index-title")  # type: ignore
406
407        # Clean the old index-body
408        self.body = None
409        index_body = self.body
410
411        # Restore the title
412        if title and str(title):
413            index_body.insert(title, position=0)  # type: ignore
414
415        # Insert default TOC style
416        if use_default_styles:
417            automatic_styles = body.get_element("//office:automatic-styles")
418            if isinstance(automatic_styles, Element):
419                for level in range(1, 11):
420                    if (
421                        automatic_styles.get_style(
422                            "paragraph", _toc_entry_style_name(level)
423                        )
424                        is None
425                    ):
426                        level_style = default_toc_level_style(level)
427                        automatic_styles.append(level_style)
428
429        # Auto-fill the index
430        outline_level = self.outline_level or 10
431        level_indexes: dict[int, int] = {}
432        for header in body.get_headers():
433            level = header.get_attribute_integer("text:outline-level") or 0
434            if level is None or level > outline_level:
435                continue
436            number_str = self._header_numbering(level_indexes, level)
437            # Make the title with "1.2.3. Title" format
438            paragraph = Paragraph(f"{number_str} {header}")
439            if use_default_styles:
440                paragraph.style = _toc_entry_style_name(level)
441            index_body.append(paragraph)  # type: ignore

Fill the TOC with the titles found in the document. A TOC is not contextual so it will catch all titles before and after its insertion. If the TOC is not attached to a document, attach it beforehand or provide one as argument.

For having a pretty TOC, let use_default_styles by default.

Arguments:

document -- Document

use_default_styles -- bool
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
xml_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protected: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key_digest_algorithm: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Tab(odfdo.Element):
194class Tab(Element):
195    """This element represents the [UNICODE] tab character (HORIZONTAL
196    TABULATION, U+0009).
197
198    The position attribute contains the number of the tab-stop to which
199    a tab character refers. The position 0 marks the start margin of a
200    paragraph. Note: The position attribute is only a hint to help non-layout
201    oriented consumers to determine the tab/tab-stop association. Layout
202    oriented consumers should determine the tab positions based on the style
203    information
204    """
205
206    _tag = "text:tab"
207    _properties: tuple[PropDef, ...] = (PropDef("position", "text:tab-ref"),)
208
209    def __init__(self, position: int | None = None, **kwargs: Any) -> None:
210        """
211        Arguments:
212
213            position -- int
214        """
215        super().__init__(**kwargs)
216        if self._do_init and position is not None and position >= 0:
217            self.position = str(position)

This element represents the [UNICODE] tab character (HORIZONTAL TABULATION, U+0009).

The position attribute contains the number of the tab-stop to which a tab character refers. The position 0 marks the start margin of a paragraph. Note: The position attribute is only a hint to help non-layout oriented consumers to determine the tab/tab-stop association. Layout oriented consumers should determine the tab positions based on the style information

Tab(position: int | None = None, **kwargs: Any)
209    def __init__(self, position: int | None = None, **kwargs: Any) -> None:
210        """
211        Arguments:
212
213            position -- int
214        """
215        super().__init__(**kwargs)
216        if self._do_init and position is not None and position >= 0:
217            self.position = str(position)

Arguments:

position -- int
position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TabStopStyle(odfdo.Element):
 94class TabStopStyle(Element):
 95    """ODF "style:tab-stop"
 96    Base style for a TOC entryBase style for a TOC entry
 97    """
 98
 99    _tag = "style:tab-stop"
100    _properties = (
101        PropDef("style_char", "style:char"),
102        PropDef("leader_color", "style:leader-color"),
103        PropDef("leader_style", "style:leader-style"),
104        PropDef("leader_text", "style:leader-text"),
105        PropDef("leader_text_style", "style:leader-text-style"),
106        PropDef("leader_type", "style:leader-type"),
107        PropDef("leader_width", "style:leader-width"),
108        PropDef("style_position", "style:position"),
109        PropDef("style_type", "style:type"),
110    )
111
112    def __init__(  # noqa: C901
113        self,
114        style_char: str | None = None,
115        leader_color: str | None = None,
116        leader_style: str | None = None,
117        leader_text: str | None = None,
118        leader_text_style: str | None = None,
119        leader_type: str | None = None,
120        leader_width: str | None = None,
121        style_position: str | None = None,
122        style_type: str | None = None,
123        **kwargs: Any,
124    ):
125        super().__init__(**kwargs)
126        if self._do_init:
127            if style_char:
128                self.style_char = style_char
129            if leader_color:
130                self.leader_color = leader_color
131            if leader_style:
132                self.leader_style = leader_style
133            if leader_text:
134                self.leader_text = leader_text
135            if leader_text_style:
136                self.leader_text_style = leader_text_style
137            if leader_type:
138                self.leader_type = leader_type
139            if leader_width:
140                self.leader_width = leader_width
141            if style_position:
142                self.style_position = style_position
143            if style_type:
144                self.style_type = style_type

ODF "style:tab-stop" Base style for a TOC entryBase style for a TOC entry

TabStopStyle( style_char: str | None = None, leader_color: str | None = None, leader_style: str | None = None, leader_text: str | None = None, leader_text_style: str | None = None, leader_type: str | None = None, leader_width: str | None = None, style_position: str | None = None, style_type: str | None = None, **kwargs: Any)
112    def __init__(  # noqa: C901
113        self,
114        style_char: str | None = None,
115        leader_color: str | None = None,
116        leader_style: str | None = None,
117        leader_text: str | None = None,
118        leader_text_style: str | None = None,
119        leader_type: str | None = None,
120        leader_width: str | None = None,
121        style_position: str | None = None,
122        style_type: str | None = None,
123        **kwargs: Any,
124    ):
125        super().__init__(**kwargs)
126        if self._do_init:
127            if style_char:
128                self.style_char = style_char
129            if leader_color:
130                self.leader_color = leader_color
131            if leader_style:
132                self.leader_style = leader_style
133            if leader_text:
134                self.leader_text = leader_text
135            if leader_text_style:
136                self.leader_text_style = leader_text_style
137            if leader_type:
138                self.leader_type = leader_type
139            if leader_width:
140                self.leader_width = leader_width
141            if style_position:
142                self.style_position = style_position
143            if style_type:
144                self.style_type = style_type
style_char: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_color: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_text: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_text_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_width: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Table(odfdo.Element):
 284class Table(Element):
 285    """ODF table "table:table" """
 286
 287    _tag = "table:table"
 288    _caching = True
 289    _append = Element.append
 290
 291    def __init__(
 292        self,
 293        name: str | None = None,
 294        width: int | None = None,
 295        height: int | None = None,
 296        protected: bool = False,
 297        protection_key: str | None = None,
 298        display: bool = True,
 299        printable: bool = True,
 300        print_ranges: list[str] | None = None,
 301        style: str | None = None,
 302        **kwargs: Any,
 303    ) -> None:
 304        """Create a table element, optionally prefilled with "height" rows of
 305        "width" cells each.
 306
 307        If the table is to be protected, a protection key must be provided,
 308        i.e. a hash value of the password.
 309
 310        If the table must not be displayed, set "display" to False.
 311
 312        If the table must not be printed, set "printable" to False. The table
 313        will not be printed when it is not displayed, whatever the value of
 314        this argument.
 315
 316        Ranges of cells to print can be provided as a list of cell ranges,
 317        e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g.
 318        "E6:K12 P6:R12".
 319
 320        You can access and modify the XML tree manually, but you probably want
 321        to use the API to access and alter cells. It will save you from
 322        handling repetitions and the same number of cells for each row.
 323
 324        If you use both the table API and the XML API, you are on your own for
 325        ensuiring model integrity.
 326
 327        Arguments:
 328
 329            name -- str
 330
 331            width -- int
 332
 333            height -- int
 334
 335            protected -- bool
 336
 337            protection_key -- str
 338
 339            display -- bool
 340
 341            printable -- bool
 342
 343            print_ranges -- list
 344
 345            style -- str
 346        """
 347        super().__init__(**kwargs)
 348        self._indexes = {}
 349        self._indexes["_cmap"] = {}
 350        self._indexes["_tmap"] = {}
 351        if self._do_init:
 352            self.name = name
 353            if protected:
 354                self.protected = protected
 355                self.set_protection_key = protection_key
 356            if not display:
 357                self.displayed = display
 358            if not printable:
 359                self.printable = printable
 360            if print_ranges:
 361                self.print_ranges = print_ranges
 362            if style:
 363                self.style = style
 364            # Prefill the table
 365            if width is not None or height is not None:
 366                width = width or 1
 367                height = height or 1
 368                # Column groups for style information
 369                columns = Column(repeated=width)
 370                self._append(columns)
 371                for _i in range(height):
 372                    row = Row(width)
 373                    self._append(row)
 374        self._compute_table_cache()
 375
 376    def __str__(self) -> str:
 377        def write_content(csv_writer: object) -> None:
 378            for values in self.iter_values():
 379                line = []
 380                for value in values:
 381                    if value is None:
 382                        value = ""
 383                    if isinstance(value, str):
 384                        value = value.strip()
 385                    line.append(value)
 386                csv_writer.writerow(line)  # type: ignore
 387
 388        out = StringIO(newline=os.linesep)
 389        csv_writer = csv.writer(
 390            out,
 391            delimiter=" ",
 392            doublequote=False,
 393            escapechar="\\",
 394            lineterminator=os.linesep,
 395            quotechar='"',
 396            quoting=csv.QUOTE_NONNUMERIC,
 397        )
 398        write_content(csv_writer)
 399        return out.getvalue()
 400
 401    def _translate_y_from_any(self, y: str | int) -> int:
 402        # "3" (couting from 1) -> 2 (couting from 0)
 403        return translate_from_any(y, self.height, 1)
 404
 405    def _translate_table_coordinates_list(
 406        self,
 407        coord: tuple | list,
 408    ) -> tuple[int | None, ...]:
 409        height = self.height
 410        width = self.width
 411        # assuming we got int values
 412        if len(coord) == 1:
 413            # It is a row
 414            y = coord[0]
 415            if y and y < 0:
 416                y = increment(y, height)
 417            return (None, y, None, y)
 418        if len(coord) == 2:
 419            # It is a row range, not a cell, because context is table
 420            y = coord[0]
 421            if y and y < 0:
 422                y = increment(y, height)
 423            t = coord[1]
 424            if t and t < 0:
 425                t = increment(t, height)
 426            return (None, y, None, t)
 427        # should be 4 int
 428        x, y, z, t = coord
 429        if x and x < 0:
 430            x = increment(x, width)
 431        if y and y < 0:
 432            y = increment(y, height)
 433        if z and z < 0:
 434            z = increment(z, width)
 435        if t and t < 0:
 436            t = increment(t, height)
 437        return (x, y, z, t)
 438
 439    def _translate_table_coordinates_str(
 440        self,
 441        coord_str: str,
 442    ) -> tuple[int | None, ...]:
 443        height = self.height
 444        width = self.width
 445        coord = convert_coordinates(coord_str)
 446        if len(coord) == 2:
 447            x, y = coord
 448            if x and x < 0:
 449                x = increment(x, width)
 450            if y and y < 0:
 451                y = increment(y, height)
 452            # extent to an area :
 453            return (x, y, x, y)
 454        x, y, z, t = coord
 455        if x and x < 0:
 456            x = increment(x, width)
 457        if y and y < 0:
 458            y = increment(y, height)
 459        if z and z < 0:
 460            z = increment(z, width)
 461        if t and t < 0:
 462            t = increment(t, height)
 463        return (x, y, z, t)
 464
 465    def _translate_table_coordinates(
 466        self,
 467        coord: tuple | list | str,
 468    ) -> tuple[int | None, ...]:
 469        if isinstance(coord, str):
 470            return self._translate_table_coordinates_str(coord)
 471        return self._translate_table_coordinates_list(coord)
 472
 473    def _translate_column_coordinates_str(
 474        self,
 475        coord_str: str,
 476    ) -> tuple[int | None, ...]:
 477        width = self.width
 478        height = self.height
 479        coord = convert_coordinates(coord_str)
 480        if len(coord) == 2:
 481            x, y = coord
 482            if x and x < 0:
 483                x = increment(x, width)
 484            if y and y < 0:
 485                y = increment(y, height)
 486            # extent to an area :
 487            return (x, y, x, y)
 488        x, y, z, t = coord
 489        if x and x < 0:
 490            x = increment(x, width)
 491        if y and y < 0:
 492            y = increment(y, height)
 493        if z and z < 0:
 494            z = increment(z, width)
 495        if t and t < 0:
 496            t = increment(t, height)
 497        return (x, y, z, t)
 498
 499    def _translate_column_coordinates_list(
 500        self,
 501        coord: tuple | list,
 502    ) -> tuple[int | None, ...]:
 503        width = self.width
 504        height = self.height
 505        # assuming we got int values
 506        if len(coord) == 1:
 507            # It is a column
 508            x = coord[0]
 509            if x and x < 0:
 510                x = increment(x, width)
 511            return (x, None, x, None)
 512        if len(coord) == 2:
 513            # It is a column range, not a cell, because context is table
 514            x = coord[0]
 515            if x and x < 0:
 516                x = increment(x, width)
 517            z = coord[1]
 518            if z and z < 0:
 519                z = increment(z, width)
 520            return (x, None, z, None)
 521        # should be 4 int
 522        x, y, z, t = coord
 523        if x and x < 0:
 524            x = increment(x, width)
 525        if y and y < 0:
 526            y = increment(y, height)
 527        if z and z < 0:
 528            z = increment(z, width)
 529        if t and t < 0:
 530            t = increment(t, height)
 531        return (x, y, z, t)
 532
 533    def _translate_column_coordinates(
 534        self,
 535        coord: tuple | list | str,
 536    ) -> tuple[int | None, ...]:
 537        if isinstance(coord, str):
 538            return self._translate_column_coordinates_str(coord)
 539        return self._translate_column_coordinates_list(coord)
 540
 541    def _translate_cell_coordinates(
 542        self,
 543        coord: tuple | list | str,
 544    ) -> tuple[int | None, int | None]:
 545        # we want an x,y result
 546        coord = convert_coordinates(coord)
 547        if len(coord) == 2:
 548            x, y = coord
 549        # If we got an area, take the first cell
 550        elif len(coord) == 4:
 551            x, y, z, t = coord
 552        else:
 553            raise ValueError(str(coord))
 554        if x and x < 0:
 555            x = increment(x, self.width)
 556        if y and y < 0:
 557            y = increment(y, self.height)
 558        return (x, y)
 559
 560    def _compute_table_cache(self) -> None:
 561        idx_repeated_seq = self.elements_repeated_sequence(
 562            _xpath_row, "table:number-rows-repeated"
 563        )
 564        self._tmap = make_cache_map(idx_repeated_seq)
 565        idx_repeated_seq = self.elements_repeated_sequence(
 566            _xpath_column, "table:number-columns-repeated"
 567        )
 568        self._cmap = make_cache_map(idx_repeated_seq)
 569
 570    def _update_width(self, row: Row) -> None:
 571        """Synchronize the number of columns if the row is bigger.
 572
 573        Append, don't insert, not to disturb the current layout.
 574        """
 575        diff = row.width - self.width
 576        if diff > 0:
 577            self.append_column(Column(repeated=diff))
 578
 579    def _get_formatted_text_normal(self, context: dict | None) -> str:
 580        result = []
 581        for row in self.traverse():
 582            for cell in row.traverse():
 583                value = cell.get_value(try_get_text=False)
 584                # None ?
 585                if value is None:
 586                    # Try with get_formatted_text on the elements
 587                    value = []
 588                    for element in cell.children:
 589                        value.append(element.get_formatted_text(context))
 590                    value = "".join(value)
 591                else:
 592                    value = str(value)
 593                result.append(value)
 594                result.append("\n")
 595            result.append("\n")
 596        return "".join(result)
 597
 598    def _get_formatted_text_rst(self, context: dict) -> str:  # noqa: C901
 599        context["no_img_level"] += 1
 600        # Strip the table => We must clone
 601        table = self.clone
 602        table.rstrip(aggressive=True)  # type: ignore
 603
 604        # Fill the rows
 605        rows = []
 606        cols_nb = 0
 607        cols_size: dict[int, int] = {}
 608        for odf_row in table.traverse():  # type: ignore
 609            row = []
 610            for i, cell in enumerate(odf_row.traverse()):
 611                value = cell.get_value(try_get_text=False)
 612                # None ?
 613                if value is None:
 614                    # Try with get_formatted_text on the elements
 615                    value = []
 616                    for element in cell.children:
 617                        value.append(element.get_formatted_text(context))
 618                    value = "".join(value)
 619                else:
 620                    value = str(value)
 621                value = value.strip()
 622                # Strip the empty columns
 623                if value:
 624                    cols_nb = max(cols_nb, i + 1)
 625                # Compute the size of each columns (at least 2)
 626                cols_size[i] = max(cols_size.get(i, 2), len(value))
 627                # Append
 628                row.append(value)
 629            rows.append(row)
 630
 631        # Nothing ?
 632        if cols_nb == 0:
 633            return ""
 634
 635        # Prevent a crash with empty columns (by example with images)
 636        for col, size in cols_size.items():
 637            if size == 0:
 638                cols_size[col] = 1
 639
 640        # Update cols_size
 641        LINE_MAX = 100
 642        COL_MIN = 16
 643
 644        free_size = LINE_MAX - (cols_nb - 1) * 3 - 4
 645        real_size = sum([cols_size[i] for i in range(cols_nb)])
 646        if real_size > free_size:
 647            factor = float(free_size) / real_size
 648
 649            for i in range(cols_nb):
 650                old_size = cols_size[i]
 651
 652                # The cell is already small
 653                if old_size <= COL_MIN:
 654                    continue
 655
 656                new_size = int(factor * old_size)
 657
 658                if new_size < COL_MIN:
 659                    new_size = COL_MIN
 660                cols_size[i] = new_size
 661
 662        # Convert !
 663        result: list[str] = [""]
 664        # Construct the first/last line
 665        line: list[str] = []
 666        for i in range(cols_nb):
 667            line.append("=" * cols_size[i])
 668            line.append(" ")
 669        line_str = "".join(line)
 670
 671        # Add the lines
 672        result.append(line_str)
 673        for row in rows:
 674            # Wrap the row
 675            wrapped_row = []
 676            for i, value in enumerate(row[:cols_nb]):
 677                wrapped_value = []
 678                for part in value.split("\n"):
 679                    # Hack to handle correctly the lists or the directives
 680                    subsequent_indent = ""
 681                    part_lstripped = part.lstrip()
 682                    if part_lstripped.startswith("-") or part_lstripped.startswith(
 683                        ".."
 684                    ):
 685                        subsequent_indent = " " * (len(part) - len(part.lstrip()) + 2)
 686                    wrapped_part = wrap(
 687                        part, width=cols_size[i], subsequent_indent=subsequent_indent
 688                    )
 689                    if wrapped_part:
 690                        wrapped_value.extend(wrapped_part)
 691                    else:
 692                        wrapped_value.append("")
 693                wrapped_row.append(wrapped_value)
 694
 695            # Append!
 696            for j in range(max([1] + [len(values) for values in wrapped_row])):
 697                txt_row: list[str] = []
 698                for i in range(cols_nb):
 699                    values = wrapped_row[i] if i < len(wrapped_row) else []
 700
 701                    # An empty cell ?
 702                    if len(values) - 1 < j or not values[j]:
 703                        if i == 0 and j == 0:
 704                            txt_row.append("..")
 705                            txt_row.append(" " * (cols_size[i] - 1))
 706                        else:
 707                            txt_row.append(" " * (cols_size[i] + 1))
 708                        continue
 709
 710                    # Not empty
 711                    value = values[j]
 712                    txt_row.append(value)
 713                    txt_row.append(" " * (cols_size[i] - len(value) + 1))
 714                result.append("".join(txt_row))
 715
 716        result.append(line_str)
 717        result.append("")
 718        result.append("")
 719        result_str = "\n".join(result)
 720
 721        context["no_img_level"] -= 1
 722        return result_str
 723
 724    def _translate_x_from_any(self, x: str | int) -> int:
 725        return translate_from_any(x, self.width, 0)
 726
 727    #
 728    # Public API
 729    #
 730
 731    def append(self, something: Element | str) -> None:
 732        """Dispatch .append() call to append_row() or append_column()."""
 733        if isinstance(something, Row):
 734            self.append_row(something)
 735        elif isinstance(something, Column):
 736            self.append_column(something)
 737        else:
 738            # probably still an error
 739            self._append(something)
 740
 741    @property
 742    def height(self) -> int:
 743        """Get the current height of the table.
 744
 745        Return: int
 746        """
 747        try:
 748            height = self._tmap[-1] + 1
 749        except Exception:
 750            height = 0
 751        return height
 752
 753    @property
 754    def width(self) -> int:
 755        """Get the current width of the table, measured on columns.
 756
 757        Rows may have different widths, use the Table API to ensure width
 758        consistency.
 759
 760        Return: int
 761        """
 762        # Columns are our reference for user expected width
 763
 764        try:
 765            width = self._cmap[-1] + 1
 766        except Exception:
 767            width = 0
 768
 769        # columns = self._get_columns()
 770        # repeated = self.xpath(
 771        #        'table:table-column/@table:number-columns-repeated')
 772        # unrepeated = len(columns) - len(repeated)
 773        # ws = sum(int(r) for r in repeated) + unrepeated
 774        # if w != ws:
 775        #    print "WARNING   ws", ws, "w", w
 776
 777        return width
 778
 779    @property
 780    def size(self) -> tuple[int, int]:
 781        """Shortcut to get the current width and height of the table.
 782
 783        Return: (int, int)
 784        """
 785        return self.width, self.height
 786
 787    @property
 788    def name(self) -> str | None:
 789        """Get / set the name of the table."""
 790        return self.get_attribute_string("table:name")
 791
 792    @name.setter
 793    def name(self, name: str) -> None:
 794        name = _table_name_check(name)
 795        # first, update named ranges
 796        # fixme : delete name ranges when deleting table, too.
 797        for named_range in self.get_named_ranges(table_name=self.name):
 798            named_range.set_table_name(name)
 799        self.set_attribute("table:name", name)
 800
 801    @property
 802    def protected(self) -> bool:
 803        return bool(self.get_attribute("table:protected"))
 804
 805    @protected.setter
 806    def protected(self, protect: bool) -> None:
 807        self.set_attribute("table:protected", protect)
 808
 809    @property
 810    def protection_key(self) -> str | None:
 811        return self.get_attribute_string("table:protection-key")
 812
 813    @protection_key.setter
 814    def protection_key(self, key: str) -> None:
 815        self.set_attribute("table:protection-key", key)
 816
 817    @property
 818    def displayed(self) -> bool:
 819        return bool(self.get_attribute("table:display"))
 820
 821    @displayed.setter
 822    def displayed(self, display: bool) -> None:
 823        self.set_attribute("table:display", display)
 824
 825    @property
 826    def printable(self) -> bool:
 827        printable = self.get_attribute("table:print")
 828        # Default value
 829        if printable is None:
 830            return True
 831        return bool(printable)
 832
 833    @printable.setter
 834    def printable(self, printable: bool) -> None:
 835        self.set_attribute("table:print", printable)
 836
 837    @property
 838    def print_ranges(self) -> list[str]:
 839        ranges = self.get_attribute_string("table:print-ranges")
 840        if isinstance(ranges, str):
 841            return ranges.split()
 842        return []
 843
 844    @print_ranges.setter
 845    def print_ranges(self, ranges: list[str] | None) -> None:
 846        if isinstance(ranges, (list, tuple)):
 847            self.set_attribute("table:print-ranges", " ".join(ranges))
 848        else:
 849            self.set_attribute("table:print-ranges", ranges)
 850
 851    @property
 852    def style(self) -> str | None:
 853        """Get / set the style of the table
 854
 855        Return: str
 856        """
 857        return self.get_attribute_string("table:style-name")
 858
 859    @style.setter
 860    def style(self, style: str | Element) -> None:
 861        self.set_style_attribute("table:style-name", style)
 862
 863    def get_formatted_text(self, context: dict | None = None) -> str:
 864        if context and context["rst_mode"]:
 865            return self._get_formatted_text_rst(context)
 866        return self._get_formatted_text_normal(context)
 867
 868    def get_values(
 869        self,
 870        coord: tuple | list | str | None = None,
 871        cell_type: str | None = None,
 872        complete: bool = True,
 873        get_type: bool = False,
 874        flat: bool = False,
 875    ) -> list:
 876        """Get a matrix of values of the table.
 877
 878        Filter by coordinates will parse the area defined by the coordinates.
 879
 880        If 'cell_type' is used and 'complete' is True (default), missing values
 881        are replaced by None.
 882        Filter by ' cell_type = "all" ' will retrieve cells of any
 883        type, aka non empty cells.
 884
 885        If 'cell_type' is None, complete is always True : with no cell type
 886        queried, get_values() returns None for each empty cell, the length
 887        each lists is equal to the width of the table.
 888
 889        If get_type is True, returns tuples (value, ODF type of value), or
 890        (None, None) for empty cells with complete True.
 891
 892        If flat is True, the methods return a single list of all the values.
 893        By default, flat is False.
 894
 895        Arguments:
 896
 897            coord -- str or tuple of int : coordinates of area
 898
 899            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
 900                         'currency', 'percentage' or 'all'
 901
 902            complete -- boolean
 903
 904            get_type -- boolean
 905
 906        Return: list of lists of Python types
 907        """
 908        if coord:
 909            x, y, z, t = self._translate_table_coordinates(coord)
 910        else:
 911            x = y = z = t = None
 912        data = []
 913        for row in self.traverse(start=y, end=t):
 914            if z is None:
 915                width = self.width
 916            else:
 917                width = min(z + 1, self.width)
 918            if x is not None:
 919                width -= x
 920            values = row.get_values(
 921                (x, z),
 922                cell_type=cell_type,
 923                complete=complete,
 924                get_type=get_type,
 925            )
 926            # complete row to match request width
 927            if complete:
 928                if get_type:
 929                    values.extend([(None, None)] * (width - len(values)))
 930                else:
 931                    values.extend([None] * (width - len(values)))
 932            if flat:
 933                data.extend(values)
 934            else:
 935                data.append(values)
 936        return data
 937
 938    def iter_values(
 939        self,
 940        coord: tuple | list | str | None = None,
 941        cell_type: str | None = None,
 942        complete: bool = True,
 943        get_type: bool = False,
 944    ) -> Iterator[list]:
 945        """Iterate through lines of Python values of the table.
 946
 947        Filter by coordinates will parse the area defined by the coordinates.
 948
 949        cell_type, complete, grt_type : see get_values()
 950
 951
 952
 953        Arguments:
 954
 955            coord -- str or tuple of int : coordinates of area
 956
 957            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
 958                         'currency', 'percentage' or 'all'
 959
 960            complete -- boolean
 961
 962            get_type -- boolean
 963
 964        Return: iterator of lists
 965        """
 966        if coord:
 967            x, y, z, t = self._translate_table_coordinates(coord)
 968        else:
 969            x = y = z = t = None
 970        for row in self.traverse(start=y, end=t):
 971            if z is None:
 972                width = self.width
 973            else:
 974                width = min(z + 1, self.width)
 975            if x is not None:
 976                width -= x
 977            values = row.get_values(
 978                (x, z),
 979                cell_type=cell_type,
 980                complete=complete,
 981                get_type=get_type,
 982            )
 983            # complete row to match column width
 984            if complete:
 985                if get_type:
 986                    values.extend([(None, None)] * (width - len(values)))
 987                else:
 988                    values.extend([None] * (width - len(values)))
 989            yield values
 990
 991    def set_values(
 992        self,
 993        values: list,
 994        coord: tuple | list | str | None = None,
 995        style: str | None = None,
 996        cell_type: str | None = None,
 997        currency: str | None = None,
 998    ) -> None:
 999        """Set the value of cells in the table, from the 'coord' position
1000        with values.
1001
1002        'coord' is the coordinate of the upper left cell to be modified by
1003        values. If 'coord' is None, default to the position (0,0) ("A1").
1004        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1005        area is used as coordinate.
1006
1007        The table is *not* cleared before the operation, to reset the table
1008        before setting values, use table.clear().
1009
1010        A list of lists is expected, with as many lists as rows, and as many
1011        items in each sublist as cells to be setted. None values in the list
1012        will create empty cells with no cell type (but eventually a style).
1013
1014        Arguments:
1015
1016            coord -- tuple or str
1017
1018            values -- list of lists of python types
1019
1020            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1021                         'string' or 'time'
1022
1023            currency -- three-letter str
1024
1025            style -- str
1026        """
1027        if coord:
1028            x, y = self._translate_cell_coordinates(coord)
1029        else:
1030            x = y = 0
1031        if y is None:
1032            y = 0
1033        if x is None:
1034            x = 0
1035        y -= 1
1036        for row_values in values:
1037            y += 1
1038            if not row_values:
1039                continue
1040            row = self.get_row(y, clone=True)
1041            repeated = row.repeated or 1
1042            if repeated >= 2:
1043                row.repeated = None
1044            row.set_values(
1045                row_values,
1046                start=x,
1047                cell_type=cell_type,
1048                currency=currency,
1049                style=style,
1050            )
1051            self.set_row(y, row, clone=False)
1052            self._update_width(row)
1053
1054    def rstrip(self, aggressive: bool = False) -> None:
1055        """Remove *in-place* empty rows below and empty cells at the right of
1056        the table. Cells are empty if they contain no value or it evaluates
1057        to False, and no style.
1058
1059        If aggressive is True, empty cells with style are removed too.
1060
1061        Argument:
1062
1063            aggressive -- bool
1064        """
1065        # Step 1: remove empty rows below the table
1066        for row in reversed(self._get_rows()):
1067            if row.is_empty(aggressive=aggressive):
1068                row.parent.delete(row)  # type: ignore
1069            else:
1070                break
1071        # Step 2: rstrip remaining rows
1072        max_width = 0
1073        for row in self._get_rows():
1074            row.rstrip(aggressive=aggressive)
1075            # keep count of the biggest row
1076            max_width = max(max_width, row.width)
1077        # raz cache of rows
1078        self._indexes["_tmap"] = {}
1079        # Step 3: trim columns to match max_width
1080        columns = self._get_columns()
1081        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1082        if not isinstance(repeated_cols, list):
1083            raise TypeError
1084        unrepeated = len(columns) - len(repeated_cols)
1085        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1086        diff = column_width - max_width
1087        if diff > 0:
1088            for column in reversed(columns):
1089                repeated = column.repeated or 1
1090                repeated = repeated - diff
1091                if repeated > 0:
1092                    column.repeated = repeated
1093                    break
1094                else:
1095                    column.parent.delete(column)
1096                    diff = -repeated
1097                    if diff == 0:
1098                        break
1099        # raz cache of columns
1100        self._indexes["_cmap"] = {}
1101        self._compute_table_cache()
1102
1103    def transpose(self, coord: tuple | list | str | None = None) -> None:  # noqa: C901
1104        """Swap *in-place* rows and columns of the table.
1105
1106        If 'coord' is not None, apply transpose only to the area defined by the
1107        coordinates. Beware, if area is not square, some cells mays be over
1108        written during the process.
1109
1110        Arguments:
1111
1112            coord -- str or tuple of int : coordinates of area
1113
1114            start -- int or str
1115        """
1116        data = []
1117        if coord is None:
1118            for row in self.traverse():
1119                data.append(list(row.traverse()))
1120            transposed_data = zip_longest(*data)
1121            self.clear()
1122            # new_rows = []
1123            for row_cells in transposed_data:
1124                if not isiterable(row_cells):
1125                    row_cells = (row_cells,)
1126                row = Row()
1127                row.extend_cells(row_cells)
1128                self.append_row(row, clone=False)
1129            self._compute_table_cache()
1130        else:
1131            x, y, z, t = self._translate_table_coordinates(coord)
1132            if x is None:
1133                x = 0
1134            else:
1135                x = min(x, self.width - 1)
1136            if z is None:
1137                z = self.width - 1
1138            else:
1139                z = min(z, self.width - 1)
1140            if y is None:
1141                y = 0
1142            else:
1143                y = min(y, self.height - 1)
1144            if t is None:
1145                t = self.height - 1
1146            else:
1147                t = min(t, self.height - 1)
1148            for row in self.traverse(start=y, end=t):
1149                data.append(list(row.traverse(start=x, end=z)))
1150            transposed_data = zip_longest(*data)
1151            # clear locally
1152            w = z - x + 1
1153            h = t - y + 1
1154            if w != h:
1155                nones = [[None] * w for i in range(h)]
1156                self.set_values(nones, coord=(x, y, z, t))
1157            # put transposed
1158            filtered_data: list[tuple[Cell]] = []
1159            for row_cells in transposed_data:
1160                if isinstance(row_cells, (list, tuple)):
1161                    filtered_data.append(row_cells)
1162                else:
1163                    filtered_data.append((row_cells,))
1164            self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1))
1165            self._compute_table_cache()
1166
1167    def is_empty(self, aggressive: bool = False) -> bool:
1168        """Return whether every cell in the table has no value or the value
1169        evaluates to False (empty string), and no style.
1170
1171        If aggressive is True, empty cells with style are considered empty.
1172
1173        Arguments:
1174
1175            aggressive -- bool
1176        """
1177        return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())
1178
1179    #
1180    # Rows
1181    #
1182
1183    def _get_rows(self) -> list[Row]:
1184        return self.get_elements(_xpath_row)  # type: ignore
1185
1186    def traverse(  # noqa: C901
1187        self,
1188        start: int | None = None,
1189        end: int | None = None,
1190    ) -> Iterator[Row]:
1191        """Yield as many row elements as expected rows in the table, i.e.
1192        expand repetitions by returning the same row as many times as
1193        necessary.
1194
1195            Arguments:
1196
1197                start -- int
1198
1199                end -- int
1200
1201        Copies are returned, use set_row() to push them back.
1202        """
1203        idx = -1
1204        before = -1
1205        y = 0
1206        if start is None and end is None:
1207            for juska in self._tmap:
1208                idx += 1
1209                if idx in self._indexes["_tmap"]:
1210                    row = self._indexes["_tmap"][idx]
1211                else:
1212                    row = self._get_element_idx2(_xpath_row_idx, idx)
1213                    self._indexes["_tmap"][idx] = row
1214                repeated = juska - before
1215                before = juska
1216                for _i in range(repeated or 1):
1217                    # Return a copy without the now obsolete repetition
1218                    row = row.clone
1219                    row.y = y
1220                    y += 1
1221                    if repeated > 1:
1222                        row.repeated = None
1223                    yield row
1224        else:
1225            if start is None:
1226                start = 0
1227            start = max(0, start)
1228            if end is None:
1229                try:
1230                    end = self._tmap[-1]
1231                except Exception:
1232                    end = -1
1233            start_map = find_odf_idx(self._tmap, start)
1234            if start_map is None:
1235                return
1236            if start_map > 0:
1237                before = self._tmap[start_map - 1]
1238            idx = start_map - 1
1239            before = start - 1
1240            y = start
1241            for juska in self._tmap[start_map:]:
1242                idx += 1
1243                if idx in self._indexes["_tmap"]:
1244                    row = self._indexes["_tmap"][idx]
1245                else:
1246                    row = self._get_element_idx2(_xpath_row_idx, idx)
1247                    self._indexes["_tmap"][idx] = row
1248                repeated = juska - before
1249                before = juska
1250                for _i in range(repeated or 1):
1251                    if y <= end:
1252                        row = row.clone
1253                        row.y = y
1254                        y += 1
1255                        if repeated > 1 or (y == start and start > 0):
1256                            row.repeated = None
1257                        yield row
1258
1259    def get_rows(
1260        self,
1261        coord: tuple | list | str | None = None,
1262        style: str | None = None,
1263        content: str | None = None,
1264    ) -> list[Row]:
1265        """Get the list of rows matching the criteria.
1266
1267        Filter by coordinates will parse the area defined by the coordinates.
1268
1269        Arguments:
1270
1271            coord -- str or tuple of int : coordinates of rows
1272
1273            content -- str regex
1274
1275            style -- str
1276
1277        Return: list of rows
1278        """
1279        if coord:
1280            _x, y, _z, t = self._translate_table_coordinates(coord)
1281        else:
1282            y = t = None
1283        # fixme : not clones ?
1284        if not content and not style:
1285            return list(self.traverse(start=y, end=t))
1286        rows = []
1287        for row in self.traverse(start=y, end=t):
1288            if content and not row.match(content):
1289                continue
1290            if style and style != row.style:
1291                continue
1292            rows.append(row)
1293        return rows
1294
1295    def _get_row2(self, y: int, clone: bool = True, create: bool = True) -> Row:
1296        if y >= self.height:
1297            if create:
1298                return Row()
1299            raise ValueError("Row not found")
1300        row = self._get_row2_base(y)
1301        if row is None:
1302            raise ValueError("Row not found")
1303        if clone:
1304            return row.clone
1305        return row
1306
1307    def _get_row2_base(self, y: int) -> Row | None:
1308        idx = find_odf_idx(self._tmap, y)
1309        if idx is not None:
1310            if idx in self._indexes["_tmap"]:
1311                row = self._indexes["_tmap"][idx]
1312            else:
1313                row = self._get_element_idx2(_xpath_row_idx, idx)
1314                self._indexes["_tmap"][idx] = row
1315            return row
1316        return None
1317
1318    def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1319        """Get the row at the given "y" position.
1320
1321        Position start at 0. So cell A4 is on row 3.
1322
1323        A copy is returned, use set_cell() to push it back.
1324
1325        Arguments:
1326
1327            y -- int or str
1328
1329        Return: Row
1330        """
1331        # fixme : keep repeat ? maybe an option to functions : "raw=False"
1332        y = self._translate_y_from_any(y)
1333        row = self._get_row2(y, clone=clone, create=create)
1334        if row is None:
1335            raise ValueError("Row not found")
1336        row.y = y
1337        return row
1338
1339    def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1340        """Replace the row at the given position with the new one. Repetions of
1341        the old row will be adjusted.
1342
1343        If row is None, a new empty row is created.
1344
1345        Position start at 0. So cell A4 is on row 3.
1346
1347        Arguments:
1348
1349            y -- int or str
1350
1351            row -- Row
1352
1353        returns the row, with updated row.y
1354        """
1355        if row is None:
1356            row = Row()
1357            repeated = 1
1358            clone = False
1359        else:
1360            repeated = row.repeated or 1
1361        y = self._translate_y_from_any(y)
1362        row.y = y
1363        # Outside the defined table ?
1364        diff = y - self.height
1365        if diff == 0:
1366            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1367        elif diff > 0:
1368            self.append_row(Row(repeated=diff), _repeated=diff, clone=clone)
1369            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1370        else:
1371            # Inside the defined table
1372            row_back = set_item_in_vault(  # type: ignore
1373                y, row, self, _xpath_row_idx, "_tmap", clone=clone
1374            )
1375        # print self.serialize(True)
1376        # Update width if necessary
1377        self._update_width(row_back)
1378        return row_back
1379
1380    def insert_row(
1381        self, y: str | int, row: Row | None = None, clone: bool = True
1382    ) -> Row:
1383        """Insert the row before the given "y" position. If no row is given,
1384        an empty one is created.
1385
1386        Position start at 0. So cell A4 is on row 3.
1387
1388        If row is None, a new empty row is created.
1389
1390        Arguments:
1391
1392            y -- int or str
1393
1394            row -- Row
1395
1396        returns the row, with updated row.y
1397        """
1398        if row is None:
1399            row = Row()
1400            clone = False
1401        y = self._translate_y_from_any(y)
1402        diff = y - self.height
1403        if diff < 0:
1404            row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap")
1405        elif diff == 0:
1406            row_back = self.append_row(row, clone=clone)
1407        else:
1408            self.append_row(Row(repeated=diff), _repeated=diff, clone=False)
1409            row_back = self.append_row(row, clone=clone)
1410        row_back.y = y  # type: ignore
1411        # Update width if necessary
1412        self._update_width(row_back)  # type: ignore
1413        return row_back  # type: ignore
1414
1415    def extend_rows(self, rows: list[Row] | None = None) -> None:
1416        """Append a list of rows at the end of the table.
1417
1418        Arguments:
1419
1420            rows -- list of Row
1421        """
1422        if rows is None:
1423            rows = []
1424        self.extend(rows)
1425        self._compute_table_cache()
1426        # Update width if necessary
1427        width = self.width
1428        for row in self.traverse():
1429            if row.width > width:
1430                width = row.width
1431        diff = width - self.width
1432        if diff > 0:
1433            self.append_column(Column(repeated=diff))
1434
1435    def append_row(
1436        self,
1437        row: Row | None = None,
1438        clone: bool = True,
1439        _repeated: int | None = None,
1440    ) -> Row:
1441        """Append the row at the end of the table. If no row is given, an
1442        empty one is created.
1443
1444        Position start at 0. So cell A4 is on row 3.
1445
1446        Note the columns are automatically created when the first row is
1447        inserted in an empty table. So better insert a filled row.
1448
1449        Arguments:
1450
1451            row -- Row
1452
1453            _repeated -- (optional), repeated value of the row
1454
1455        returns the row, with updated row.y
1456        """
1457        if row is None:
1458            row = Row()
1459            _repeated = 1
1460        elif clone:
1461            row = row.clone
1462        # Appending a repeated row accepted
1463        # Do not insert next to the last row because it could be in a group
1464        self._append(row)
1465        if _repeated is None:
1466            _repeated = row.repeated or 1
1467        self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated)
1468        row.y = self.height - 1
1469        # Initialize columns
1470        if not self._get_columns():
1471            repeated = row.width
1472            self.insert(Column(repeated=repeated), position=0)
1473            self._compute_table_cache()
1474        # Update width if necessary
1475        self._update_width(row)
1476        return row
1477
1478    def delete_row(self, y: int | str) -> None:
1479        """Delete the row at the given "y" position.
1480
1481        Position start at 0. So cell A4 is on row 3.
1482
1483        Arguments:
1484
1485            y -- int or str
1486        """
1487        y = self._translate_y_from_any(y)
1488        # Outside the defined table
1489        if y >= self.height:
1490            return
1491        # Inside the defined table
1492        delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")
1493
1494    def get_row_values(
1495        self,
1496        y: int | str,
1497        cell_type: str | None = None,
1498        complete: bool = True,
1499        get_type: bool = False,
1500    ) -> list:
1501        """Shortcut to get the list of Python values for the cells of the row
1502        at the given "y" position.
1503
1504        Position start at 0. So cell A4 is on row 3.
1505
1506        Filter by cell_type, with cell_type 'all' will retrieve cells of any
1507        type, aka non empty cells.
1508        If cell_type and complete is True, replace missing values by None.
1509
1510        If get_type is True, returns a tuple (value, ODF type of value)
1511
1512        Arguments:
1513
1514            y -- int, str
1515
1516            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1517                         'currency', 'percentage' or 'all'
1518
1519            complete -- boolean
1520
1521            get_type -- boolean
1522
1523        Return: list of lists of Python types
1524        """
1525        values = self.get_row(y, clone=False).get_values(
1526            cell_type=cell_type, complete=complete, get_type=get_type
1527        )
1528        # complete row to match column width
1529        if complete:
1530            if get_type:
1531                values.extend([(None, None)] * (self.width - len(values)))
1532            else:
1533                values.extend([None] * (self.width - len(values)))
1534        return values
1535
1536    def set_row_values(
1537        self,
1538        y: int | str,
1539        values: list,
1540        cell_type: str | None = None,
1541        currency: str | None = None,
1542        style: str | None = None,
1543    ) -> Row:
1544        """Shortcut to set the values of *all* cells of the row at the given
1545        "y" position.
1546
1547        Position start at 0. So cell A4 is on row 3.
1548
1549        Arguments:
1550
1551            y -- int or str
1552
1553            values -- list of Python types
1554
1555            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1556                         'string' or 'time'
1557
1558            currency -- three-letter str
1559
1560            style -- str
1561
1562        returns the row, with updated row.y
1563        """
1564        row = Row()  # needed if clones rows
1565        row.set_values(values, style=style, cell_type=cell_type, currency=currency)
1566        return self.set_row(y, row)  # needed if clones rows
1567
1568    def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1569        """Shortcut to set *all* the cells of the row at the given
1570        "y" position.
1571
1572        Position start at 0. So cell A4 is on row 3.
1573
1574        Arguments:
1575
1576            y -- int or str
1577
1578            cells -- list of Python types
1579
1580            style -- str
1581
1582        returns the row, with updated row.y
1583        """
1584        if cells is None:
1585            cells = []
1586        row = Row()  # needed if clones rows
1587        row.extend_cells(cells)
1588        return self.set_row(y, row)  # needed if clones rows
1589
1590    def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1591        """Return wether every cell in the row at the given "y" position has
1592        no value or the value evaluates to False (empty string), and no style.
1593
1594        Position start at 0. So cell A4 is on row 3.
1595
1596        If aggressive is True, empty cells with style are considered empty.
1597
1598        Arguments:
1599
1600            y -- int or str
1601
1602            aggressive -- bool
1603        """
1604        return self.get_row(y, clone=False).is_empty(aggressive=aggressive)
1605
1606    #
1607    # Cells
1608    #
1609
1610    def get_cells(
1611        self,
1612        coord: tuple | list | str | None = None,
1613        cell_type: str | None = None,
1614        style: str | None = None,
1615        content: str | None = None,
1616        flat: bool = False,
1617    ) -> list:
1618        """Get the cells matching the criteria. If 'coord' is None,
1619        parse the whole table, else parse the area defined by 'coord'.
1620
1621        Filter by  cell_type = "all"  will retrieve cells of any
1622        type, aka non empty cells.
1623
1624        If flat is True (default is False), the method return a single list
1625        of all the values, else a list of lists of cells.
1626
1627        if cell_type, style and content are None, get_cells() will return
1628        the exact number of cells of the area, including empty cells.
1629
1630        Arguments:
1631
1632            coordinates -- str or tuple of int : coordinates of area
1633
1634            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1635                         'currency', 'percentage' or 'all'
1636
1637            content -- str regex
1638
1639            style -- str
1640
1641            flat -- boolean
1642
1643        Return: list of tuples
1644        """
1645        if coord:
1646            x, y, z, t = self._translate_table_coordinates(coord)
1647        else:
1648            x = y = z = t = None
1649        if flat:
1650            cells: list[Cell] = []
1651            for row in self.traverse(start=y, end=t):
1652                row_cells = row.get_cells(
1653                    coord=(x, z),
1654                    cell_type=cell_type,
1655                    style=style,
1656                    content=content,
1657                )
1658                cells.extend(row_cells)
1659            return cells
1660        else:
1661            lcells: list[list[Cell]] = []
1662            for row in self.traverse(start=y, end=t):
1663                row_cells = row.get_cells(
1664                    coord=(x, z),
1665                    cell_type=cell_type,
1666                    style=style,
1667                    content=content,
1668                )
1669                lcells.append(row_cells)
1670            return lcells
1671
1672    def get_cell(
1673        self,
1674        coord: tuple | list | str,
1675        clone: bool = True,
1676        keep_repeated: bool = True,
1677    ) -> Cell:
1678        """Get the cell at the given coordinates.
1679
1680        They are either a 2-uplet of (x, y) starting from 0, or a
1681        human-readable position like "C4".
1682
1683        A copy is returned, use ``set_cell`` to push it back.
1684
1685        Arguments:
1686
1687            coord -- (int, int) or str
1688
1689        Return: Cell
1690        """
1691        x, y = self._translate_cell_coordinates(coord)
1692        if x is None:
1693            raise ValueError
1694        if y is None:
1695            raise ValueError
1696        # Outside the defined table
1697        if y >= self.height:
1698            cell = Cell()
1699        else:
1700            # Inside the defined table
1701            row = self._get_row2_base(y)
1702            if row is None:
1703                raise ValueError
1704            read_cell = row.get_cell(x, clone=clone)
1705            if read_cell is None:
1706                raise ValueError
1707            cell = read_cell
1708            if not keep_repeated:
1709                repeated = cell.repeated or 1
1710                if repeated >= 2:
1711                    cell.repeated = None
1712        cell.x = x
1713        cell.y = y
1714        return cell
1715
1716    def get_value(
1717        self,
1718        coord: tuple | list | str,
1719        get_type: bool = False,
1720    ) -> Any:
1721        """Shortcut to get the Python value of the cell at the given
1722        coordinates.
1723
1724        If get_type is True, returns the tuples (value, ODF type)
1725
1726        coord is either a 2-uplet of (x, y) starting from 0, or a
1727        human-readable position like "C4". If an Area is given, the upper
1728        left position is used as coord.
1729
1730        Arguments:
1731
1732            coord -- (int, int) or str : coordinate
1733
1734        Return: Python type
1735        """
1736        x, y = self._translate_cell_coordinates(coord)
1737        if x is None:
1738            raise ValueError
1739        if y is None:
1740            raise ValueError
1741        # Outside the defined table
1742        if y >= self.height:
1743            if get_type:
1744                return (None, None)
1745            return None
1746        else:
1747            # Inside the defined table
1748            row = self._get_row2_base(y)
1749            if row is None:
1750                raise ValueError
1751            cell = row._get_cell2_base(x)
1752            if cell is None:
1753                if get_type:
1754                    return (None, None)
1755                return None
1756            return cell.get_value(get_type=get_type)
1757
1758    def set_cell(
1759        self,
1760        coord: tuple | list | str,
1761        cell: Cell | None = None,
1762        clone: bool = True,
1763    ) -> Cell:
1764        """Replace a cell of the table at the given coordinates.
1765
1766        They are either a 2-uplet of (x, y) starting from 0, or a
1767        human-readable position like "C4".
1768
1769        Arguments:
1770
1771            coord -- (int, int) or str : coordinate
1772
1773            cell -- Cell
1774
1775        return the cell, with x and y updated
1776        """
1777        if cell is None:
1778            cell = Cell()
1779            clone = False
1780        x, y = self._translate_cell_coordinates(coord)
1781        if x is None:
1782            raise ValueError
1783        if y is None:
1784            raise ValueError
1785        cell.x = x
1786        cell.y = y
1787        if y >= self.height:
1788            row = Row()
1789            cell_back = row.set_cell(x, cell, clone=clone)
1790            self.set_row(y, row, clone=False)
1791        else:
1792            row_read = self._get_row2_base(y)
1793            if row_read is None:
1794                raise ValueError
1795            row = row_read
1796            row.y = y
1797            repeated = row.repeated or 1
1798            if repeated > 1:
1799                row = row.clone
1800                row.repeated = None
1801                cell_back = row.set_cell(x, cell, clone=clone)
1802                self.set_row(y, row, clone=False)
1803            else:
1804                cell_back = row.set_cell(x, cell, clone=clone)
1805                # Update width if necessary, since we don't use set_row
1806                self._update_width(row)
1807        return cell_back
1808
1809    def set_cells(
1810        self,
1811        cells: list[list[Cell]] | list[tuple[Cell]],
1812        coord: tuple | list | str | None = None,
1813        clone: bool = True,
1814    ) -> None:
1815        """Set the cells in the table, from the 'coord' position.
1816
1817        'coord' is the coordinate of the upper left cell to be modified by
1818        values. If 'coord' is None, default to the position (0,0) ("A1").
1819        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1820        area is used as coordinate.
1821
1822        The table is *not* cleared before the operation, to reset the table
1823        before setting cells, use table.clear().
1824
1825        A list of lists is expected, with as many lists as rows to be set, and
1826        as many cell in each sublist as cells to be setted in the row.
1827
1828        Arguments:
1829
1830            cells -- list of list of cells
1831
1832            coord -- tuple or str
1833
1834            values -- list of lists of python types
1835        """
1836        if coord:
1837            x, y = self._translate_cell_coordinates(coord)
1838        else:
1839            x = y = 0
1840        if y is None:
1841            y = 0
1842        if x is None:
1843            x = 0
1844        y -= 1
1845        for row_cells in cells:
1846            y += 1
1847            if not row_cells:
1848                continue
1849            row = self.get_row(y, clone=True)
1850            repeated = row.repeated or 1
1851            if repeated >= 2:
1852                row.repeated = None
1853            row.set_cells(row_cells, start=x, clone=clone)
1854            self.set_row(y, row, clone=False)
1855            self._update_width(row)
1856
1857    def set_value(
1858        self,
1859        coord: tuple | list | str,
1860        value: Any,
1861        cell_type: str | None = None,
1862        currency: str | None = None,
1863        style: str | None = None,
1864    ) -> None:
1865        """Set the Python value of the cell at the given coordinates.
1866
1867        They are either a 2-uplet of (x, y) starting from 0, or a
1868        human-readable position like "C4".
1869
1870        Arguments:
1871
1872            coord -- (int, int) or str
1873
1874            value -- Python type
1875
1876            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1877                     'string' or 'time'
1878
1879            currency -- three-letter str
1880
1881            style -- str
1882
1883        """
1884        self.set_cell(
1885            coord,
1886            Cell(value, cell_type=cell_type, currency=currency, style=style),
1887            clone=False,
1888        )
1889
1890    def set_cell_image(
1891        self,
1892        coord: tuple | list | str,
1893        image_frame: Frame,
1894        doc_type: str | None = None,
1895    ) -> None:
1896        """Do all the magic to display an image in the cell at the given
1897        coordinates.
1898
1899        They are either a 2-uplet of (x, y) starting from 0, or a
1900        human-readable position like "C4".
1901
1902        The frame element must contain the expected image position and
1903        dimensions.
1904
1905        DrawImage insertion depends on the document type, so the type must be
1906        provided or the table element must be already attached to a document.
1907
1908        Arguments:
1909
1910            coord -- (int, int) or str
1911
1912            image_frame -- Frame including an image
1913
1914            doc_type -- 'spreadsheet' or 'text'
1915        """
1916        # Test document type
1917        if doc_type is None:
1918            body = self.document_body
1919            if body is None:
1920                raise ValueError("document type not found")
1921            doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get(
1922                body.tag
1923            )
1924            if doc_type is None:
1925                raise ValueError("document type not supported for images")
1926        # We need the end address of the image
1927        x, y = self._translate_cell_coordinates(coord)
1928        if x is None:
1929            raise ValueError
1930        if y is None:
1931            raise ValueError
1932        cell = self.get_cell((x, y))
1933        image_frame = image_frame.clone  # type: ignore
1934        # Remove any previous paragraph, frame, etc.
1935        for child in cell.children:
1936            cell.delete(child)
1937        # Now it all depends on the document type
1938        if doc_type == "spreadsheet":
1939            image_frame.anchor_type = "char"
1940            # The frame needs end coordinates
1941            width, height = image_frame.size
1942            image_frame.set_attribute("table:end-x", width)
1943            image_frame.set_attribute("table:end-y", height)
1944            # FIXME what happens when the address changes?
1945            address = f"{self.name}.{digit_to_alpha(x)}{y + 1}"
1946            image_frame.set_attribute("table:end-cell-address", address)
1947            # The frame is directly in the cell
1948            cell.append(image_frame)
1949        elif doc_type == "text":
1950            # The frame must be in a paragraph
1951            cell.set_value("")
1952            paragraph = cell.get_element("text:p")
1953            if paragraph is None:
1954                raise ValueError
1955            paragraph.append(image_frame)
1956        self.set_cell(coord, cell)
1957
1958    def insert_cell(
1959        self,
1960        coord: tuple | list | str,
1961        cell: Cell | None = None,
1962        clone: bool = True,
1963    ) -> Cell:
1964        """Insert the given cell at the given coordinates. If no cell is
1965        given, an empty one is created.
1966
1967        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
1968        human-readable position like "C4".
1969
1970        Cells on the right are shifted. Other rows remain untouched.
1971
1972        Arguments:
1973
1974            coord -- (int, int) or str
1975
1976            cell -- Cell
1977
1978        returns the cell with x and y updated
1979        """
1980        if cell is None:
1981            cell = Cell()
1982            clone = False
1983        if clone:
1984            cell = cell.clone
1985        x, y = self._translate_cell_coordinates(coord)
1986        if x is None:
1987            raise ValueError
1988        if y is None:
1989            raise ValueError
1990        row = self._get_row2(y, clone=True)
1991        row.y = y
1992        row.repeated = None
1993        cell_back = row.insert_cell(x, cell, clone=False)
1994        self.set_row(y, row, clone=False)
1995        # Update width if necessary
1996        self._update_width(row)
1997        return cell_back
1998
1999    def append_cell(
2000        self,
2001        y: int | str,
2002        cell: Cell | None = None,
2003        clone: bool = True,
2004    ) -> Cell:
2005        """Append the given cell at the "y" coordinate. Repeated cells are
2006        accepted. If no cell is given, an empty one is created.
2007
2008        Position start at 0. So cell A4 is on row 3.
2009
2010        Other rows remain untouched.
2011
2012        Arguments:
2013
2014            y -- int or str
2015
2016            cell -- Cell
2017
2018        returns the cell with x and y updated
2019        """
2020        if cell is None:
2021            cell = Cell()
2022            clone = False
2023        if clone:
2024            cell = cell.clone
2025        y = self._translate_y_from_any(y)
2026        row = self._get_row2(y)
2027        row.y = y
2028        cell_back = row.append_cell(cell, clone=False)
2029        self.set_row(y, row)
2030        # Update width if necessary
2031        self._update_width(row)
2032        return cell_back
2033
2034    def delete_cell(self, coord: tuple | list | str) -> None:
2035        """Delete the cell at the given coordinates, so that next cells are
2036        shifted to the left.
2037
2038        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2039        human-readable position like "C4".
2040
2041        Use set_value() for erasing value.
2042
2043        Arguments:
2044
2045            coord -- (int, int) or str
2046        """
2047        x, y = self._translate_cell_coordinates(coord)
2048        if x is None:
2049            raise ValueError
2050        if y is None:
2051            raise ValueError
2052        # Outside the defined table
2053        if y >= self.height:
2054            return
2055        # Inside the defined table
2056        row = self._get_row2_base(y)
2057        if row is None:
2058            raise ValueError
2059        row.delete_cell(x)
2060        # self.set_row(y, row)
2061
2062    # Columns
2063
2064    def _get_columns(self) -> list:
2065        return self.get_elements(_xpath_column)
2066
2067    def traverse_columns(  # noqa: C901
2068        self,
2069        start: int | None = None,
2070        end: int | None = None,
2071    ) -> Iterator[Column]:
2072        """Yield as many column elements as expected columns in the table,
2073        i.e. expand repetitions by returning the same column as many times as
2074        necessary.
2075
2076            Arguments:
2077
2078                start -- int
2079
2080                end -- int
2081
2082        Copies are returned, use set_column() to push them back.
2083        """
2084        idx = -1
2085        before = -1
2086        x = 0
2087        if start is None and end is None:
2088            for juska in self._cmap:
2089                idx += 1
2090                if idx in self._indexes["_cmap"]:
2091                    column = self._indexes["_cmap"][idx]
2092                else:
2093                    column = self._get_element_idx2(_xpath_column_idx, idx)
2094                    self._indexes["_cmap"][idx] = column
2095                repeated = juska - before
2096                before = juska
2097                for _i in range(repeated or 1):
2098                    # Return a copy without the now obsolete repetition
2099                    column = column.clone
2100                    column.x = x
2101                    x += 1
2102                    if repeated > 1:
2103                        column.repeated = None
2104                    yield column
2105        else:
2106            if start is None:
2107                start = 0
2108            start = max(0, start)
2109            if end is None:
2110                try:
2111                    end = self._cmap[-1]
2112                except Exception:
2113                    end = -1
2114            start_map = find_odf_idx(self._cmap, start)
2115            if start_map is None:
2116                return
2117            if start_map > 0:
2118                before = self._cmap[start_map - 1]
2119            idx = start_map - 1
2120            before = start - 1
2121            x = start
2122            for juska in self._cmap[start_map:]:
2123                idx += 1
2124                if idx in self._indexes["_cmap"]:
2125                    column = self._indexes["_cmap"][idx]
2126                else:
2127                    column = self._get_element_idx2(_xpath_column_idx, idx)
2128                    self._indexes["_cmap"][idx] = column
2129                repeated = juska - before
2130                before = juska
2131                for _i in range(repeated or 1):
2132                    if x <= end:
2133                        column = column.clone
2134                        column.x = x
2135                        x += 1
2136                        if repeated > 1 or (x == start and start > 0):
2137                            column.repeated = None
2138                        yield column
2139
2140    def get_columns(
2141        self,
2142        coord: tuple | list | str | None = None,
2143        style: str | None = None,
2144    ) -> list[Column]:
2145        """Get the list of columns matching the criteria. Each result is a
2146        tuple of (x, column).
2147
2148        Arguments:
2149
2150            coord -- str or tuple of int : coordinates of columns
2151
2152            style -- str
2153
2154        Return: list of columns
2155        """
2156        if coord:
2157            x, _y, _z, t = self._translate_column_coordinates(coord)
2158        else:
2159            x = t = None
2160        if not style:
2161            return list(self.traverse_columns(start=x, end=t))
2162        columns = []
2163        for column in self.traverse_columns(start=x, end=t):
2164            if style != column.style:
2165                continue
2166            columns.append(column)
2167        return columns
2168
2169    def _get_column2(self, x: int) -> Column | None:
2170        # Outside the defined table
2171        if x >= self.width:
2172            return Column()
2173        # Inside the defined table
2174        odf_idx = find_odf_idx(self._cmap, x)
2175        if odf_idx is not None:
2176            column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2177            if column is None:
2178                return None
2179            # fixme : no clone here => change doc and unit tests
2180            return column.clone  # type: ignore
2181            # return row
2182        return None
2183
2184    def get_column(self, x: int | str) -> Column:
2185        """Get the column at the given "x" position.
2186
2187        ODF columns don't contain cells, only style information.
2188
2189        Position start at 0. So cell C4 is on column 2. Alphabetical position
2190        like "C" is accepted.
2191
2192        A copy is returned, use set_column() to push it back.
2193
2194        Arguments:
2195
2196            x -- int or str
2197
2198        Return: Column
2199        """
2200        x = self._translate_x_from_any(x)
2201        column = self._get_column2(x)
2202        if column is None:
2203            raise ValueError
2204        column.x = x
2205        return column
2206
2207    def set_column(
2208        self,
2209        x: int | str,
2210        column: Column | None = None,
2211    ) -> Column:
2212        """Replace the column at the given "x" position.
2213
2214        ODF columns don't contain cells, only style information.
2215
2216        Position start at 0. So cell C4 is on column 2. Alphabetical position
2217        like "C" is accepted.
2218
2219        Arguments:
2220
2221            x -- int or str
2222
2223            column -- Column
2224        """
2225        x = self._translate_x_from_any(x)
2226        if column is None:
2227            column = Column()
2228            repeated = 1
2229        else:
2230            repeated = column.repeated or 1
2231        column.x = x
2232        # Outside the defined table ?
2233        diff = x - self.width
2234        if diff == 0:
2235            column_back = self.append_column(column, _repeated=repeated)
2236        elif diff > 0:
2237            self.append_column(Column(repeated=diff), _repeated=diff)
2238            column_back = self.append_column(column, _repeated=repeated)
2239        else:
2240            # Inside the defined table
2241            column_back = set_item_in_vault(  # type: ignore
2242                x, column, self, _xpath_column_idx, "_cmap"
2243            )
2244        return column_back
2245
2246    def insert_column(
2247        self,
2248        x: int | str,
2249        column: Column | None = None,
2250    ) -> Column:
2251        """Insert the column before the given "x" position. If no column is
2252        given, an empty one is created.
2253
2254        ODF columns don't contain cells, only style information.
2255
2256        Position start at 0. So cell C4 is on column 2. Alphabetical position
2257        like "C" is accepted.
2258
2259        Arguments:
2260
2261            x -- int or str
2262
2263            column -- Column
2264        """
2265        if column is None:
2266            column = Column()
2267        x = self._translate_x_from_any(x)
2268        diff = x - self.width
2269        if diff < 0:
2270            column_back = insert_item_in_vault(
2271                x, column, self, _xpath_column_idx, "_cmap"
2272            )
2273        elif diff == 0:
2274            column_back = self.append_column(column.clone)
2275        else:
2276            self.append_column(Column(repeated=diff), _repeated=diff)
2277            column_back = self.append_column(column.clone)
2278        column_back.x = x  # type: ignore
2279        # Repetitions are accepted
2280        repeated = column.repeated or 1
2281        # Update width on every row
2282        for row in self._get_rows():
2283            if row.width > x:
2284                row.insert_cell(x, Cell(repeated=repeated))
2285            # Shorter rows don't need insert
2286            # Longer rows shouldn't exist!
2287        return column_back  # type: ignore
2288
2289    def append_column(
2290        self,
2291        column: Column | None = None,
2292        _repeated: int | None = None,
2293    ) -> Column:
2294        """Append the column at the end of the table. If no column is given,
2295        an empty one is created.
2296
2297        ODF columns don't contain cells, only style information.
2298
2299        Position start at 0. So cell C4 is on column 2. Alphabetical position
2300        like "C" is accepted.
2301
2302        Arguments:
2303
2304            column -- Column
2305        """
2306        if column is None:
2307            column = Column()
2308        else:
2309            column = column.clone
2310        if not self._cmap:
2311            position = 0
2312        else:
2313            odf_idx = len(self._cmap) - 1
2314            last_column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2315            if last_column is None:
2316                raise ValueError
2317            position = self.index(last_column) + 1
2318        column.x = self.width
2319        self.insert(column, position=position)
2320        # Repetitions are accepted
2321        if _repeated is None:
2322            _repeated = column.repeated or 1
2323        self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated)
2324        # No need to update row widths
2325        return column
2326
2327    def delete_column(self, x: int | str) -> None:
2328        """Delete the column at the given position. ODF columns don't contain
2329        cells, only style information.
2330
2331        Position start at 0. So cell C4 is on column 2. Alphabetical position
2332        like "C" is accepted.
2333
2334        Arguments:
2335
2336            x -- int or str
2337        """
2338        x = self._translate_x_from_any(x)
2339        # Outside the defined table
2340        if x >= self.width:
2341            return
2342        # Inside the defined table
2343        delete_item_in_vault(x, self, _xpath_column_idx, "_cmap")
2344        # Update width
2345        width = self.width
2346        for row in self._get_rows():
2347            if row.width >= width:
2348                row.delete_cell(x)
2349
2350    def get_column_cells(  # noqa: C901
2351        self,
2352        x: int | str,
2353        style: str | None = None,
2354        content: str | None = None,
2355        cell_type: str | None = None,
2356        complete: bool = False,
2357    ) -> list[Cell | None]:
2358        """Get the list of cells at the given position.
2359
2360        Position start at 0. So cell C4 is on column 2. Alphabetical position
2361        like "C" is accepted.
2362
2363        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2364        type, aka non empty cells.
2365
2366        If complete is True, replace missing values by None.
2367
2368        Arguments:
2369
2370            x -- int or str
2371
2372            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2373                         'currency', 'percentage' or 'all'
2374
2375            content -- str regex
2376
2377            style -- str
2378
2379            complete -- boolean
2380
2381        Return: list of Cell
2382        """
2383        x = self._translate_x_from_any(x)
2384        if cell_type:
2385            cell_type = cell_type.lower().strip()
2386        cells: list[Cell | None] = []
2387        if not style and not content and not cell_type:
2388            for row in self.traverse():
2389                cells.append(row.get_cell(x, clone=True))
2390            return cells
2391        for row in self.traverse():
2392            cell = row.get_cell(x, clone=True)
2393            if cell is None:
2394                raise ValueError
2395            # Filter the cells by cell_type
2396            if cell_type:
2397                ctype = cell.type
2398                if not ctype or not (ctype == cell_type or cell_type == "all"):
2399                    if complete:
2400                        cells.append(None)
2401                    continue
2402            # Filter the cells with the regex
2403            if content and not cell.match(content):
2404                if complete:
2405                    cells.append(None)
2406                continue
2407            # Filter the cells with the style
2408            if style and style != cell.style:
2409                if complete:
2410                    cells.append(None)
2411                continue
2412            cells.append(cell)
2413        return cells
2414
2415    def get_column_values(
2416        self,
2417        x: int | str,
2418        cell_type: str | None = None,
2419        complete: bool = True,
2420        get_type: bool = False,
2421    ) -> list[Any]:
2422        """Shortcut to get the list of Python values for the cells at the
2423        given position.
2424
2425        Position start at 0. So cell C4 is on column 2. Alphabetical position
2426        like "C" is accepted.
2427
2428        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2429        type, aka non empty cells.
2430        If cell_type and complete is True, replace missing values by None.
2431
2432        If get_type is True, returns a tuple (value, ODF type of value)
2433
2434        Arguments:
2435
2436            x -- int or str
2437
2438            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2439                         'currency', 'percentage' or 'all'
2440
2441            complete -- boolean
2442
2443            get_type -- boolean
2444
2445        Return: list of Python types
2446        """
2447        cells = self.get_column_cells(
2448            x, style=None, content=None, cell_type=cell_type, complete=complete
2449        )
2450        values: list[Any] = []
2451        for cell in cells:
2452            if cell is None:
2453                if complete:
2454                    if get_type:
2455                        values.append((None, None))
2456                    else:
2457                        values.append(None)
2458                continue
2459            if cell_type:
2460                ctype = cell.type
2461                if not ctype or not (ctype == cell_type or cell_type == "all"):
2462                    if complete:
2463                        if get_type:
2464                            values.append((None, None))
2465                        else:
2466                            values.append(None)
2467                    continue
2468            values.append(cell.get_value(get_type=get_type))
2469        return values
2470
2471    def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2472        """Shortcut to set the list of cells at the given position.
2473
2474        Position start at 0. So cell C4 is on column 2. Alphabetical position
2475        like "C" is accepted.
2476
2477        The list must have the same length than the table height.
2478
2479        Arguments:
2480
2481            x -- int or str
2482
2483            cells -- list of Cell
2484        """
2485        height = self.height
2486        if len(cells) != height:
2487            raise ValueError(f"col mismatch: {height} cells expected")
2488        cells_iterator = iter(cells)
2489        for y, row in enumerate(self.traverse()):
2490            row.set_cell(x, next(cells_iterator))
2491            self.set_row(y, row)
2492
2493    def set_column_values(
2494        self,
2495        x: int | str,
2496        values: list,
2497        cell_type: str | None = None,
2498        currency: str | None = None,
2499        style: str | None = None,
2500    ) -> None:
2501        """Shortcut to set the list of Python values of cells at the given
2502        position.
2503
2504        Position start at 0. So cell C4 is on column 2. Alphabetical position
2505        like "C" is accepted.
2506
2507        The list must have the same length than the table height.
2508
2509        Arguments:
2510
2511            x -- int or str
2512
2513            values -- list of Python types
2514
2515            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
2516                         'string' or 'time'
2517
2518            currency -- three-letter str
2519
2520            style -- str
2521        """
2522        cells = [
2523            Cell(value, cell_type=cell_type, currency=currency, style=style)
2524            for value in values
2525        ]
2526        self.set_column_cells(x, cells)
2527
2528    def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2529        """Return wether every cell in the column at "x" position has no value
2530        or the value evaluates to False (empty string), and no style.
2531
2532        Position start at 0. So cell C4 is on column 2. Alphabetical position
2533        like "C" is accepted.
2534
2535        If aggressive is True, empty cells with style are considered empty.
2536
2537        Return: bool
2538        """
2539        for cell in self.get_column_cells(x):
2540            if cell is None:
2541                continue
2542            if not cell.is_empty(aggressive=aggressive):
2543                return False
2544        return True
2545
2546    # Named Range
2547
2548    def get_named_ranges(  # type: ignore
2549        self,
2550        table_name: str | list[str] | None = None,
2551    ) -> list[NamedRange]:
2552        """Returns the list of available Name Ranges of the spreadsheet. If
2553        table_name is provided, limits the search to these tables.
2554        Beware : named ranges are stored at the body level, thus do not call
2555        this method on a cloned table.
2556
2557        Arguments:
2558
2559            table_names -- str or list of str, names of tables
2560
2561        Return : list of table_range
2562        """
2563        body = self.document_body
2564        if not body:
2565            return []
2566        all_named_ranges = body.get_named_ranges()
2567        if not table_name:
2568            return all_named_ranges  # type:ignore
2569        filter_ = []
2570        if isinstance(table_name, str):
2571            filter_.append(table_name)
2572        elif isiterable(table_name):
2573            filter_.extend(table_name)
2574        else:
2575            raise ValueError(
2576                f"table_name must be string or Iterable, not {type(table_name)}"
2577            )
2578        return [
2579            nr for nr in all_named_ranges if nr.table_name in filter_  # type:ignore
2580        ]
2581
2582    def get_named_range(self, name: str) -> NamedRange:
2583        """Returns the Name Ranges of the specified name. If
2584        table_name is provided, limits the search to these tables.
2585        Beware : named ranges are stored at the body level, thus do not call
2586        this method on a cloned table.
2587
2588        Arguments:
2589
2590            name -- str, name of the named range object
2591
2592        Return : NamedRange
2593        """
2594        body = self.document_body
2595        if not body:
2596            raise ValueError("Table is not inside a document")
2597        return body.get_named_range(name)  # type: ignore
2598
2599    def set_named_range(
2600        self,
2601        name: str,
2602        crange: str | tuple | list,
2603        table_name: str | None = None,
2604        usage: str | None = None,
2605    ) -> None:
2606        """Create a Named Range element and insert it in the document.
2607        Beware : named ranges are stored at the body level, thus do not call
2608        this method on a cloned table.
2609
2610        Arguments:
2611
2612            name -- str, name of the named range
2613
2614            crange -- str or tuple of int, cell or area coordinate
2615
2616            table_name -- str, name of the table
2617
2618            uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2619        """
2620        body = self.document_body
2621        if not body:
2622            raise ValueError("Table is not inside a document")
2623        if not name:
2624            raise ValueError("Name required.")
2625        if table_name is None:
2626            table_name = self.name
2627        named_range = NamedRange(name, crange, table_name, usage)
2628        body.append_named_range(named_range)
2629
2630    def delete_named_range(self, name: str) -> None:
2631        """Delete the Named Range of specified name from the spreadsheet.
2632        Beware : named ranges are stored at the body level, thus do not call
2633        this method on a cloned table.
2634
2635        Arguments:
2636
2637            name -- str
2638        """
2639        name = name.strip()
2640        if not name:
2641            raise ValueError("Name required.")
2642        body = self.document_body
2643        if not body:
2644            raise ValueError("Table is not inside a document.")
2645        body.delete_named_range(name)
2646
2647    #
2648    # Cell span
2649    #
2650
2651    def set_span(  # noqa: C901
2652        self,
2653        area: str | tuple | list,
2654        merge: bool = False,
2655    ) -> bool:
2656        """Create a Cell Span : span the first cell of the area on several
2657        columns and/or rows.
2658        If merge is True, replace text of the cell by the concatenation of
2659        existing text in covered cells.
2660        Beware : if merge is True, old text is changed, if merge is False
2661        (the default), old text in coverd cells is still present but not
2662        displayed by most GUI.
2663
2664        If the area defines only one cell, the set span will do nothing.
2665        It is not allowed to apply set span to an area whose one cell already
2666        belongs to previous cell span.
2667
2668        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2669        be provided as an alpha numeric value like "A1:B2' or a tuple like
2670        (0, 0, 1, 1) or (0, 0).
2671
2672        Arguments:
2673
2674            area -- str or tuple of int, cell or area coordinate
2675
2676            merge -- boolean
2677        """
2678        # get area
2679        digits = convert_coordinates(area)
2680        if len(digits) == 4:
2681            x, y, z, t = digits
2682        else:
2683            x, y = digits
2684            z, t = digits
2685        start = x, y
2686        end = z, t
2687        if start == end:
2688            # one cell : do nothing
2689            return False
2690        if x is None:
2691            raise ValueError
2692        if y is None:
2693            raise ValueError
2694        if z is None:
2695            raise ValueError
2696        if t is None:
2697            raise ValueError
2698        # check for previous span
2699        good = True
2700        # Check boundaries and empty cells : need to crate non existent cells
2701        # so don't use get_cells directly, but get_cell
2702        cells = []
2703        for yy in range(y, t + 1):
2704            row_cells = []
2705            for xx in range(x, z + 1):
2706                row_cells.append(
2707                    self.get_cell((xx, yy), clone=True, keep_repeated=False)
2708                )
2709            cells.append(row_cells)
2710        for row in cells:
2711            for cell in row:
2712                if cell._is_spanned():
2713                    good = False
2714                    break
2715            if not good:
2716                break
2717        if not good:
2718            return False
2719        # Check boundaries
2720        # if z >= self.width or t >= self.height:
2721        #    self.set_cell(coord = end)
2722        #    print area, z, t
2723        #    cells = self.get_cells((x, y, z, t))
2724        #    print cells
2725        # do it:
2726        if merge:
2727            val_list = []
2728            for row in cells:
2729                for cell in row:
2730                    if cell.is_empty(aggressive=True):
2731                        continue
2732                    val = cell.get_value()
2733                    if val is not None:
2734                        if isinstance(val, str):
2735                            val.strip()
2736                        if val != "":
2737                            val_list.append(val)
2738                        cell.clear()
2739            if val_list:
2740                if len(val_list) == 1:
2741                    cells[0][0].set_value(val_list[0])
2742                else:
2743                    value = " ".join([str(v) for v in val_list if v])
2744                    cells[0][0].set_value(value)
2745        cols = z - x + 1
2746        cells[0][0].set_attribute("table:number-columns-spanned", str(cols))
2747        rows = t - y + 1
2748        cells[0][0].set_attribute("table:number-rows-spanned", str(rows))
2749        for cell in cells[0][1:]:
2750            cell.tag = "table:covered-table-cell"
2751        for row in cells[1:]:
2752            for cell in row:
2753                cell.tag = "table:covered-table-cell"
2754        # replace cells in table
2755        self.set_cells(cells, coord=start, clone=False)
2756        return True
2757
2758    def del_span(self, area: str | tuple | list) -> bool:
2759        """Delete a Cell Span. 'area' is the cell coordiante of the upper left
2760        cell of the spanned area.
2761
2762        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2763        be provided as an alpha numeric value like "A1:B2' or a tuple like
2764        (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell
2765        is used.
2766
2767        Arguments:
2768
2769            area -- str or tuple of int, cell or area coordinate
2770        """
2771        # get area
2772        digits = convert_coordinates(area)
2773        if len(digits) == 4:
2774            x, y, _z, _t = digits
2775        else:
2776            x, y = digits
2777        if x is None:
2778            raise ValueError
2779        if y is None:
2780            raise ValueError
2781        start = x, y
2782        # check for previous span
2783        cell0 = self.get_cell(start)
2784        nb_cols = cell0.get_attribute_integer("table:number-columns-spanned")
2785        if nb_cols is None:
2786            return False
2787        nb_rows = cell0.get_attribute_integer("table:number-rows-spanned")
2788        if nb_rows is None:
2789            return False
2790        z = x + nb_cols - 1
2791        t = y + nb_rows - 1
2792        cells = self.get_cells((x, y, z, t))
2793        cells[0][0].del_attribute("table:number-columns-spanned")
2794        cells[0][0].del_attribute("table:number-rows-spanned")
2795        for cell in cells[0][1:]:
2796            cell.tag = "table:table-cell"
2797        for row in cells[1:]:
2798            for cell in row:
2799                cell.tag = "table:table-cell"
2800        # replace cells in table
2801        self.set_cells(cells, coord=start, clone=False)
2802        return True
2803
2804    # Utilities
2805
2806    def to_csv(
2807        self,
2808        path_or_file: str | Path | None = None,
2809        dialect: str = "excel",
2810    ) -> Any:
2811        """Write the table as CSV in the file.
2812
2813        If the file is a string, it is opened as a local path. Else an
2814        opened file-like is expected.
2815
2816        Arguments:
2817
2818            path_or_file -- str or file-like
2819
2820            dialect -- str, python csv.dialect, can be 'excel', 'unix'...
2821        """
2822
2823        def write_content(csv_writer: object) -> None:
2824            for values in self.iter_values():
2825                line = []
2826                for value in values:
2827                    if value is None:
2828                        value = ""
2829                    if isinstance(value, str):
2830                        value = value.strip()
2831                    line.append(value)
2832                csv_writer.writerow(line)  # type: ignore
2833
2834        out = StringIO(newline="")
2835        csv_writer = csv.writer(out, dialect=dialect)
2836        write_content(csv_writer)
2837        if path_or_file is None:
2838            return out.getvalue()
2839        path = Path(path_or_file)
2840        path.write_text(out.getvalue())
2841        return None

ODF table "table:table"

Table( name: str | None = None, width: int | None = None, height: int | None = None, protected: bool = False, protection_key: str | None = None, display: bool = True, printable: bool = True, print_ranges: list[str] | None = None, style: str | None = None, **kwargs: Any)
291    def __init__(
292        self,
293        name: str | None = None,
294        width: int | None = None,
295        height: int | None = None,
296        protected: bool = False,
297        protection_key: str | None = None,
298        display: bool = True,
299        printable: bool = True,
300        print_ranges: list[str] | None = None,
301        style: str | None = None,
302        **kwargs: Any,
303    ) -> None:
304        """Create a table element, optionally prefilled with "height" rows of
305        "width" cells each.
306
307        If the table is to be protected, a protection key must be provided,
308        i.e. a hash value of the password.
309
310        If the table must not be displayed, set "display" to False.
311
312        If the table must not be printed, set "printable" to False. The table
313        will not be printed when it is not displayed, whatever the value of
314        this argument.
315
316        Ranges of cells to print can be provided as a list of cell ranges,
317        e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g.
318        "E6:K12 P6:R12".
319
320        You can access and modify the XML tree manually, but you probably want
321        to use the API to access and alter cells. It will save you from
322        handling repetitions and the same number of cells for each row.
323
324        If you use both the table API and the XML API, you are on your own for
325        ensuiring model integrity.
326
327        Arguments:
328
329            name -- str
330
331            width -- int
332
333            height -- int
334
335            protected -- bool
336
337            protection_key -- str
338
339            display -- bool
340
341            printable -- bool
342
343            print_ranges -- list
344
345            style -- str
346        """
347        super().__init__(**kwargs)
348        self._indexes = {}
349        self._indexes["_cmap"] = {}
350        self._indexes["_tmap"] = {}
351        if self._do_init:
352            self.name = name
353            if protected:
354                self.protected = protected
355                self.set_protection_key = protection_key
356            if not display:
357                self.displayed = display
358            if not printable:
359                self.printable = printable
360            if print_ranges:
361                self.print_ranges = print_ranges
362            if style:
363                self.style = style
364            # Prefill the table
365            if width is not None or height is not None:
366                width = width or 1
367                height = height or 1
368                # Column groups for style information
369                columns = Column(repeated=width)
370                self._append(columns)
371                for _i in range(height):
372                    row = Row(width)
373                    self._append(row)
374        self._compute_table_cache()

Create a table element, optionally prefilled with "height" rows of "width" cells each.

If the table is to be protected, a protection key must be provided, i.e. a hash value of the password.

If the table must not be displayed, set "display" to False.

If the table must not be printed, set "printable" to False. The table will not be printed when it is not displayed, whatever the value of this argument.

Ranges of cells to print can be provided as a list of cell ranges, e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. "E6:K12 P6:R12".

You can access and modify the XML tree manually, but you probably want to use the API to access and alter cells. It will save you from handling repetitions and the same number of cells for each row.

If you use both the table API and the XML API, you are on your own for ensuiring model integrity.

Arguments:

name -- str

width -- int

height -- int

protected -- bool

protection_key -- str

display -- bool

printable -- bool

print_ranges -- list

style -- str
def append(self, something: Element | str) -> None:
731    def append(self, something: Element | str) -> None:
732        """Dispatch .append() call to append_row() or append_column()."""
733        if isinstance(something, Row):
734            self.append_row(something)
735        elif isinstance(something, Column):
736            self.append_column(something)
737        else:
738            # probably still an error
739            self._append(something)

Dispatch .append() call to append_row() or append_column().

height: int
741    @property
742    def height(self) -> int:
743        """Get the current height of the table.
744
745        Return: int
746        """
747        try:
748            height = self._tmap[-1] + 1
749        except Exception:
750            height = 0
751        return height

Get the current height of the table.

Return: int

width: int
753    @property
754    def width(self) -> int:
755        """Get the current width of the table, measured on columns.
756
757        Rows may have different widths, use the Table API to ensure width
758        consistency.
759
760        Return: int
761        """
762        # Columns are our reference for user expected width
763
764        try:
765            width = self._cmap[-1] + 1
766        except Exception:
767            width = 0
768
769        # columns = self._get_columns()
770        # repeated = self.xpath(
771        #        'table:table-column/@table:number-columns-repeated')
772        # unrepeated = len(columns) - len(repeated)
773        # ws = sum(int(r) for r in repeated) + unrepeated
774        # if w != ws:
775        #    print "WARNING   ws", ws, "w", w
776
777        return width

Get the current width of the table, measured on columns.

Rows may have different widths, use the Table API to ensure width consistency.

Return: int

size: tuple[int, int]
779    @property
780    def size(self) -> tuple[int, int]:
781        """Shortcut to get the current width and height of the table.
782
783        Return: (int, int)
784        """
785        return self.width, self.height

Shortcut to get the current width and height of the table.

Return: (int, int)

name: str | None
787    @property
788    def name(self) -> str | None:
789        """Get / set the name of the table."""
790        return self.get_attribute_string("table:name")

Get / set the name of the table.

protected: bool
801    @property
802    def protected(self) -> bool:
803        return bool(self.get_attribute("table:protected"))
protection_key: str | None
809    @property
810    def protection_key(self) -> str | None:
811        return self.get_attribute_string("table:protection-key")
displayed: bool
817    @property
818    def displayed(self) -> bool:
819        return bool(self.get_attribute("table:display"))
printable: bool
825    @property
826    def printable(self) -> bool:
827        printable = self.get_attribute("table:print")
828        # Default value
829        if printable is None:
830            return True
831        return bool(printable)
print_ranges: list[str]
837    @property
838    def print_ranges(self) -> list[str]:
839        ranges = self.get_attribute_string("table:print-ranges")
840        if isinstance(ranges, str):
841            return ranges.split()
842        return []
style: str | None
851    @property
852    def style(self) -> str | None:
853        """Get / set the style of the table
854
855        Return: str
856        """
857        return self.get_attribute_string("table:style-name")

Get / set the style of the table

Return: str

def get_formatted_text(self, context: dict | None = None) -> str:
863    def get_formatted_text(self, context: dict | None = None) -> str:
864        if context and context["rst_mode"]:
865            return self._get_formatted_text_rst(context)
866        return self._get_formatted_text_normal(context)

This function should return a beautiful version of the text.

def get_values( self, coord: tuple | list | str | None = None, cell_type: str | None = None, complete: bool = True, get_type: bool = False, flat: bool = False) -> list:
868    def get_values(
869        self,
870        coord: tuple | list | str | None = None,
871        cell_type: str | None = None,
872        complete: bool = True,
873        get_type: bool = False,
874        flat: bool = False,
875    ) -> list:
876        """Get a matrix of values of the table.
877
878        Filter by coordinates will parse the area defined by the coordinates.
879
880        If 'cell_type' is used and 'complete' is True (default), missing values
881        are replaced by None.
882        Filter by ' cell_type = "all" ' will retrieve cells of any
883        type, aka non empty cells.
884
885        If 'cell_type' is None, complete is always True : with no cell type
886        queried, get_values() returns None for each empty cell, the length
887        each lists is equal to the width of the table.
888
889        If get_type is True, returns tuples (value, ODF type of value), or
890        (None, None) for empty cells with complete True.
891
892        If flat is True, the methods return a single list of all the values.
893        By default, flat is False.
894
895        Arguments:
896
897            coord -- str or tuple of int : coordinates of area
898
899            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
900                         'currency', 'percentage' or 'all'
901
902            complete -- boolean
903
904            get_type -- boolean
905
906        Return: list of lists of Python types
907        """
908        if coord:
909            x, y, z, t = self._translate_table_coordinates(coord)
910        else:
911            x = y = z = t = None
912        data = []
913        for row in self.traverse(start=y, end=t):
914            if z is None:
915                width = self.width
916            else:
917                width = min(z + 1, self.width)
918            if x is not None:
919                width -= x
920            values = row.get_values(
921                (x, z),
922                cell_type=cell_type,
923                complete=complete,
924                get_type=get_type,
925            )
926            # complete row to match request width
927            if complete:
928                if get_type:
929                    values.extend([(None, None)] * (width - len(values)))
930                else:
931                    values.extend([None] * (width - len(values)))
932            if flat:
933                data.extend(values)
934            else:
935                data.append(values)
936        return data

Get a matrix of values of the table.

Filter by coordinates will parse the area defined by the coordinates.

If 'cell_type' is used and 'complete' is True (default), missing values are replaced by None. Filter by ' cell_type = "all" ' will retrieve cells of any type, aka non empty cells.

If 'cell_type' is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length each lists is equal to the width of the table.

If get_type is True, returns tuples (value, ODF type of value), or (None, None) for empty cells with complete True.

If flat is True, the methods return a single list of all the values. By default, flat is False.

Arguments:

coord -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of lists of Python types

def iter_values( self, coord: tuple | list | str | None = None, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> collections.abc.Iterator[list]:
938    def iter_values(
939        self,
940        coord: tuple | list | str | None = None,
941        cell_type: str | None = None,
942        complete: bool = True,
943        get_type: bool = False,
944    ) -> Iterator[list]:
945        """Iterate through lines of Python values of the table.
946
947        Filter by coordinates will parse the area defined by the coordinates.
948
949        cell_type, complete, grt_type : see get_values()
950
951
952
953        Arguments:
954
955            coord -- str or tuple of int : coordinates of area
956
957            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
958                         'currency', 'percentage' or 'all'
959
960            complete -- boolean
961
962            get_type -- boolean
963
964        Return: iterator of lists
965        """
966        if coord:
967            x, y, z, t = self._translate_table_coordinates(coord)
968        else:
969            x = y = z = t = None
970        for row in self.traverse(start=y, end=t):
971            if z is None:
972                width = self.width
973            else:
974                width = min(z + 1, self.width)
975            if x is not None:
976                width -= x
977            values = row.get_values(
978                (x, z),
979                cell_type=cell_type,
980                complete=complete,
981                get_type=get_type,
982            )
983            # complete row to match column width
984            if complete:
985                if get_type:
986                    values.extend([(None, None)] * (width - len(values)))
987                else:
988                    values.extend([None] * (width - len(values)))
989            yield values

Iterate through lines of Python values of the table.

Filter by coordinates will parse the area defined by the coordinates.

cell_type, complete, grt_type : see get_values()

Arguments:

coord -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: iterator of lists

def set_values( self, values: list, coord: tuple | list | str | None = None, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
 991    def set_values(
 992        self,
 993        values: list,
 994        coord: tuple | list | str | None = None,
 995        style: str | None = None,
 996        cell_type: str | None = None,
 997        currency: str | None = None,
 998    ) -> None:
 999        """Set the value of cells in the table, from the 'coord' position
1000        with values.
1001
1002        'coord' is the coordinate of the upper left cell to be modified by
1003        values. If 'coord' is None, default to the position (0,0) ("A1").
1004        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1005        area is used as coordinate.
1006
1007        The table is *not* cleared before the operation, to reset the table
1008        before setting values, use table.clear().
1009
1010        A list of lists is expected, with as many lists as rows, and as many
1011        items in each sublist as cells to be setted. None values in the list
1012        will create empty cells with no cell type (but eventually a style).
1013
1014        Arguments:
1015
1016            coord -- tuple or str
1017
1018            values -- list of lists of python types
1019
1020            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1021                         'string' or 'time'
1022
1023            currency -- three-letter str
1024
1025            style -- str
1026        """
1027        if coord:
1028            x, y = self._translate_cell_coordinates(coord)
1029        else:
1030            x = y = 0
1031        if y is None:
1032            y = 0
1033        if x is None:
1034            x = 0
1035        y -= 1
1036        for row_values in values:
1037            y += 1
1038            if not row_values:
1039                continue
1040            row = self.get_row(y, clone=True)
1041            repeated = row.repeated or 1
1042            if repeated >= 2:
1043                row.repeated = None
1044            row.set_values(
1045                row_values,
1046                start=x,
1047                cell_type=cell_type,
1048                currency=currency,
1049                style=style,
1050            )
1051            self.set_row(y, row, clone=False)
1052            self._update_width(row)

Set the value of cells in the table, from the 'coord' position with values.

'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.

The table is not cleared before the operation, to reset the table before setting values, use table.clear().

A list of lists is expected, with as many lists as rows, and as many items in each sublist as cells to be setted. None values in the list will create empty cells with no cell type (but eventually a style).

Arguments:

coord -- tuple or str

values -- list of lists of python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str
def rstrip(self, aggressive: bool = False) -> None:
1054    def rstrip(self, aggressive: bool = False) -> None:
1055        """Remove *in-place* empty rows below and empty cells at the right of
1056        the table. Cells are empty if they contain no value or it evaluates
1057        to False, and no style.
1058
1059        If aggressive is True, empty cells with style are removed too.
1060
1061        Argument:
1062
1063            aggressive -- bool
1064        """
1065        # Step 1: remove empty rows below the table
1066        for row in reversed(self._get_rows()):
1067            if row.is_empty(aggressive=aggressive):
1068                row.parent.delete(row)  # type: ignore
1069            else:
1070                break
1071        # Step 2: rstrip remaining rows
1072        max_width = 0
1073        for row in self._get_rows():
1074            row.rstrip(aggressive=aggressive)
1075            # keep count of the biggest row
1076            max_width = max(max_width, row.width)
1077        # raz cache of rows
1078        self._indexes["_tmap"] = {}
1079        # Step 3: trim columns to match max_width
1080        columns = self._get_columns()
1081        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1082        if not isinstance(repeated_cols, list):
1083            raise TypeError
1084        unrepeated = len(columns) - len(repeated_cols)
1085        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1086        diff = column_width - max_width
1087        if diff > 0:
1088            for column in reversed(columns):
1089                repeated = column.repeated or 1
1090                repeated = repeated - diff
1091                if repeated > 0:
1092                    column.repeated = repeated
1093                    break
1094                else:
1095                    column.parent.delete(column)
1096                    diff = -repeated
1097                    if diff == 0:
1098                        break
1099        # raz cache of columns
1100        self._indexes["_cmap"] = {}
1101        self._compute_table_cache()

Remove in-place empty rows below and empty cells at the right of the table. Cells are empty if they contain no value or it evaluates to False, and no style.

If aggressive is True, empty cells with style are removed too.

Argument:

aggressive -- bool
def transpose(self, coord: tuple | list | str | None = None) -> None:
1103    def transpose(self, coord: tuple | list | str | None = None) -> None:  # noqa: C901
1104        """Swap *in-place* rows and columns of the table.
1105
1106        If 'coord' is not None, apply transpose only to the area defined by the
1107        coordinates. Beware, if area is not square, some cells mays be over
1108        written during the process.
1109
1110        Arguments:
1111
1112            coord -- str or tuple of int : coordinates of area
1113
1114            start -- int or str
1115        """
1116        data = []
1117        if coord is None:
1118            for row in self.traverse():
1119                data.append(list(row.traverse()))
1120            transposed_data = zip_longest(*data)
1121            self.clear()
1122            # new_rows = []
1123            for row_cells in transposed_data:
1124                if not isiterable(row_cells):
1125                    row_cells = (row_cells,)
1126                row = Row()
1127                row.extend_cells(row_cells)
1128                self.append_row(row, clone=False)
1129            self._compute_table_cache()
1130        else:
1131            x, y, z, t = self._translate_table_coordinates(coord)
1132            if x is None:
1133                x = 0
1134            else:
1135                x = min(x, self.width - 1)
1136            if z is None:
1137                z = self.width - 1
1138            else:
1139                z = min(z, self.width - 1)
1140            if y is None:
1141                y = 0
1142            else:
1143                y = min(y, self.height - 1)
1144            if t is None:
1145                t = self.height - 1
1146            else:
1147                t = min(t, self.height - 1)
1148            for row in self.traverse(start=y, end=t):
1149                data.append(list(row.traverse(start=x, end=z)))
1150            transposed_data = zip_longest(*data)
1151            # clear locally
1152            w = z - x + 1
1153            h = t - y + 1
1154            if w != h:
1155                nones = [[None] * w for i in range(h)]
1156                self.set_values(nones, coord=(x, y, z, t))
1157            # put transposed
1158            filtered_data: list[tuple[Cell]] = []
1159            for row_cells in transposed_data:
1160                if isinstance(row_cells, (list, tuple)):
1161                    filtered_data.append(row_cells)
1162                else:
1163                    filtered_data.append((row_cells,))
1164            self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1))
1165            self._compute_table_cache()

Swap in-place rows and columns of the table.

If 'coord' is not None, apply transpose only to the area defined by the coordinates. Beware, if area is not square, some cells mays be over written during the process.

Arguments:

coord -- str or tuple of int : coordinates of area

start -- int or str
def is_empty(self, aggressive: bool = False) -> bool:
1167    def is_empty(self, aggressive: bool = False) -> bool:
1168        """Return whether every cell in the table has no value or the value
1169        evaluates to False (empty string), and no style.
1170
1171        If aggressive is True, empty cells with style are considered empty.
1172
1173        Arguments:
1174
1175            aggressive -- bool
1176        """
1177        return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())

Return whether every cell in the table has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool
def traverse( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Row]:
1186    def traverse(  # noqa: C901
1187        self,
1188        start: int | None = None,
1189        end: int | None = None,
1190    ) -> Iterator[Row]:
1191        """Yield as many row elements as expected rows in the table, i.e.
1192        expand repetitions by returning the same row as many times as
1193        necessary.
1194
1195            Arguments:
1196
1197                start -- int
1198
1199                end -- int
1200
1201        Copies are returned, use set_row() to push them back.
1202        """
1203        idx = -1
1204        before = -1
1205        y = 0
1206        if start is None and end is None:
1207            for juska in self._tmap:
1208                idx += 1
1209                if idx in self._indexes["_tmap"]:
1210                    row = self._indexes["_tmap"][idx]
1211                else:
1212                    row = self._get_element_idx2(_xpath_row_idx, idx)
1213                    self._indexes["_tmap"][idx] = row
1214                repeated = juska - before
1215                before = juska
1216                for _i in range(repeated or 1):
1217                    # Return a copy without the now obsolete repetition
1218                    row = row.clone
1219                    row.y = y
1220                    y += 1
1221                    if repeated > 1:
1222                        row.repeated = None
1223                    yield row
1224        else:
1225            if start is None:
1226                start = 0
1227            start = max(0, start)
1228            if end is None:
1229                try:
1230                    end = self._tmap[-1]
1231                except Exception:
1232                    end = -1
1233            start_map = find_odf_idx(self._tmap, start)
1234            if start_map is None:
1235                return
1236            if start_map > 0:
1237                before = self._tmap[start_map - 1]
1238            idx = start_map - 1
1239            before = start - 1
1240            y = start
1241            for juska in self._tmap[start_map:]:
1242                idx += 1
1243                if idx in self._indexes["_tmap"]:
1244                    row = self._indexes["_tmap"][idx]
1245                else:
1246                    row = self._get_element_idx2(_xpath_row_idx, idx)
1247                    self._indexes["_tmap"][idx] = row
1248                repeated = juska - before
1249                before = juska
1250                for _i in range(repeated or 1):
1251                    if y <= end:
1252                        row = row.clone
1253                        row.y = y
1254                        y += 1
1255                        if repeated > 1 or (y == start and start > 0):
1256                            row.repeated = None
1257                        yield row

Yield as many row elements as expected rows in the table, i.e. expand repetitions by returning the same row as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_row() to push them back.

def get_rows( self, coord: tuple | list | str | None = None, style: str | None = None, content: str | None = None) -> list[Row]:
1259    def get_rows(
1260        self,
1261        coord: tuple | list | str | None = None,
1262        style: str | None = None,
1263        content: str | None = None,
1264    ) -> list[Row]:
1265        """Get the list of rows matching the criteria.
1266
1267        Filter by coordinates will parse the area defined by the coordinates.
1268
1269        Arguments:
1270
1271            coord -- str or tuple of int : coordinates of rows
1272
1273            content -- str regex
1274
1275            style -- str
1276
1277        Return: list of rows
1278        """
1279        if coord:
1280            _x, y, _z, t = self._translate_table_coordinates(coord)
1281        else:
1282            y = t = None
1283        # fixme : not clones ?
1284        if not content and not style:
1285            return list(self.traverse(start=y, end=t))
1286        rows = []
1287        for row in self.traverse(start=y, end=t):
1288            if content and not row.match(content):
1289                continue
1290            if style and style != row.style:
1291                continue
1292            rows.append(row)
1293        return rows

Get the list of rows matching the criteria.

Filter by coordinates will parse the area defined by the coordinates.

Arguments:

coord -- str or tuple of int : coordinates of rows

content -- str regex

style -- str

Return: list of rows

def get_row( self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1318    def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1319        """Get the row at the given "y" position.
1320
1321        Position start at 0. So cell A4 is on row 3.
1322
1323        A copy is returned, use set_cell() to push it back.
1324
1325        Arguments:
1326
1327            y -- int or str
1328
1329        Return: Row
1330        """
1331        # fixme : keep repeat ? maybe an option to functions : "raw=False"
1332        y = self._translate_y_from_any(y)
1333        row = self._get_row2(y, clone=clone, create=create)
1334        if row is None:
1335            raise ValueError("Row not found")
1336        row.y = y
1337        return row

Get the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

A copy is returned, use set_cell() to push it back.

Arguments:

y -- int or str

Return: Row

def set_row( self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1339    def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1340        """Replace the row at the given position with the new one. Repetions of
1341        the old row will be adjusted.
1342
1343        If row is None, a new empty row is created.
1344
1345        Position start at 0. So cell A4 is on row 3.
1346
1347        Arguments:
1348
1349            y -- int or str
1350
1351            row -- Row
1352
1353        returns the row, with updated row.y
1354        """
1355        if row is None:
1356            row = Row()
1357            repeated = 1
1358            clone = False
1359        else:
1360            repeated = row.repeated or 1
1361        y = self._translate_y_from_any(y)
1362        row.y = y
1363        # Outside the defined table ?
1364        diff = y - self.height
1365        if diff == 0:
1366            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1367        elif diff > 0:
1368            self.append_row(Row(repeated=diff), _repeated=diff, clone=clone)
1369            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1370        else:
1371            # Inside the defined table
1372            row_back = set_item_in_vault(  # type: ignore
1373                y, row, self, _xpath_row_idx, "_tmap", clone=clone
1374            )
1375        # print self.serialize(True)
1376        # Update width if necessary
1377        self._update_width(row_back)
1378        return row_back

Replace the row at the given position with the new one. Repetions of the old row will be adjusted.

If row is None, a new empty row is created.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

row -- Row

returns the row, with updated row.y

def insert_row( self, y: str | int, row: Row | None = None, clone: bool = True) -> Row:
1380    def insert_row(
1381        self, y: str | int, row: Row | None = None, clone: bool = True
1382    ) -> Row:
1383        """Insert the row before the given "y" position. If no row is given,
1384        an empty one is created.
1385
1386        Position start at 0. So cell A4 is on row 3.
1387
1388        If row is None, a new empty row is created.
1389
1390        Arguments:
1391
1392            y -- int or str
1393
1394            row -- Row
1395
1396        returns the row, with updated row.y
1397        """
1398        if row is None:
1399            row = Row()
1400            clone = False
1401        y = self._translate_y_from_any(y)
1402        diff = y - self.height
1403        if diff < 0:
1404            row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap")
1405        elif diff == 0:
1406            row_back = self.append_row(row, clone=clone)
1407        else:
1408            self.append_row(Row(repeated=diff), _repeated=diff, clone=False)
1409            row_back = self.append_row(row, clone=clone)
1410        row_back.y = y  # type: ignore
1411        # Update width if necessary
1412        self._update_width(row_back)  # type: ignore
1413        return row_back  # type: ignore

Insert the row before the given "y" position. If no row is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

If row is None, a new empty row is created.

Arguments:

y -- int or str

row -- Row

returns the row, with updated row.y

def extend_rows(self, rows: list[Row] | None = None) -> None:
1415    def extend_rows(self, rows: list[Row] | None = None) -> None:
1416        """Append a list of rows at the end of the table.
1417
1418        Arguments:
1419
1420            rows -- list of Row
1421        """
1422        if rows is None:
1423            rows = []
1424        self.extend(rows)
1425        self._compute_table_cache()
1426        # Update width if necessary
1427        width = self.width
1428        for row in self.traverse():
1429            if row.width > width:
1430                width = row.width
1431        diff = width - self.width
1432        if diff > 0:
1433            self.append_column(Column(repeated=diff))

Append a list of rows at the end of the table.

Arguments:

rows -- list of Row
def append_row( self, row: Row | None = None, clone: bool = True, _repeated: int | None = None) -> Row:
1435    def append_row(
1436        self,
1437        row: Row | None = None,
1438        clone: bool = True,
1439        _repeated: int | None = None,
1440    ) -> Row:
1441        """Append the row at the end of the table. If no row is given, an
1442        empty one is created.
1443
1444        Position start at 0. So cell A4 is on row 3.
1445
1446        Note the columns are automatically created when the first row is
1447        inserted in an empty table. So better insert a filled row.
1448
1449        Arguments:
1450
1451            row -- Row
1452
1453            _repeated -- (optional), repeated value of the row
1454
1455        returns the row, with updated row.y
1456        """
1457        if row is None:
1458            row = Row()
1459            _repeated = 1
1460        elif clone:
1461            row = row.clone
1462        # Appending a repeated row accepted
1463        # Do not insert next to the last row because it could be in a group
1464        self._append(row)
1465        if _repeated is None:
1466            _repeated = row.repeated or 1
1467        self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated)
1468        row.y = self.height - 1
1469        # Initialize columns
1470        if not self._get_columns():
1471            repeated = row.width
1472            self.insert(Column(repeated=repeated), position=0)
1473            self._compute_table_cache()
1474        # Update width if necessary
1475        self._update_width(row)
1476        return row

Append the row at the end of the table. If no row is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

Note the columns are automatically created when the first row is inserted in an empty table. So better insert a filled row.

Arguments:

row -- Row

_repeated -- (optional), repeated value of the row

returns the row, with updated row.y

def delete_row(self, y: int | str) -> None:
1478    def delete_row(self, y: int | str) -> None:
1479        """Delete the row at the given "y" position.
1480
1481        Position start at 0. So cell A4 is on row 3.
1482
1483        Arguments:
1484
1485            y -- int or str
1486        """
1487        y = self._translate_y_from_any(y)
1488        # Outside the defined table
1489        if y >= self.height:
1490            return
1491        # Inside the defined table
1492        delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")

Delete the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str
def get_row_values( self, y: int | str, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> list:
1494    def get_row_values(
1495        self,
1496        y: int | str,
1497        cell_type: str | None = None,
1498        complete: bool = True,
1499        get_type: bool = False,
1500    ) -> list:
1501        """Shortcut to get the list of Python values for the cells of the row
1502        at the given "y" position.
1503
1504        Position start at 0. So cell A4 is on row 3.
1505
1506        Filter by cell_type, with cell_type 'all' will retrieve cells of any
1507        type, aka non empty cells.
1508        If cell_type and complete is True, replace missing values by None.
1509
1510        If get_type is True, returns a tuple (value, ODF type of value)
1511
1512        Arguments:
1513
1514            y -- int, str
1515
1516            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1517                         'currency', 'percentage' or 'all'
1518
1519            complete -- boolean
1520
1521            get_type -- boolean
1522
1523        Return: list of lists of Python types
1524        """
1525        values = self.get_row(y, clone=False).get_values(
1526            cell_type=cell_type, complete=complete, get_type=get_type
1527        )
1528        # complete row to match column width
1529        if complete:
1530            if get_type:
1531                values.extend([(None, None)] * (self.width - len(values)))
1532            else:
1533                values.extend([None] * (self.width - len(values)))
1534        return values

Shortcut to get the list of Python values for the cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.

If get_type is True, returns a tuple (value, ODF type of value)

Arguments:

y -- int, str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of lists of Python types

def set_row_values( self, y: int | str, values: list, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> Row:
1536    def set_row_values(
1537        self,
1538        y: int | str,
1539        values: list,
1540        cell_type: str | None = None,
1541        currency: str | None = None,
1542        style: str | None = None,
1543    ) -> Row:
1544        """Shortcut to set the values of *all* cells of the row at the given
1545        "y" position.
1546
1547        Position start at 0. So cell A4 is on row 3.
1548
1549        Arguments:
1550
1551            y -- int or str
1552
1553            values -- list of Python types
1554
1555            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1556                         'string' or 'time'
1557
1558            currency -- three-letter str
1559
1560            style -- str
1561
1562        returns the row, with updated row.y
1563        """
1564        row = Row()  # needed if clones rows
1565        row.set_values(values, style=style, cell_type=cell_type, currency=currency)
1566        return self.set_row(y, row)  # needed if clones rows

Shortcut to set the values of all cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

values -- list of Python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str

returns the row, with updated row.y

def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1568    def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1569        """Shortcut to set *all* the cells of the row at the given
1570        "y" position.
1571
1572        Position start at 0. So cell A4 is on row 3.
1573
1574        Arguments:
1575
1576            y -- int or str
1577
1578            cells -- list of Python types
1579
1580            style -- str
1581
1582        returns the row, with updated row.y
1583        """
1584        if cells is None:
1585            cells = []
1586        row = Row()  # needed if clones rows
1587        row.extend_cells(cells)
1588        return self.set_row(y, row)  # needed if clones rows

Shortcut to set all the cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

cells -- list of Python types

style -- str

returns the row, with updated row.y

def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1590    def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1591        """Return wether every cell in the row at the given "y" position has
1592        no value or the value evaluates to False (empty string), and no style.
1593
1594        Position start at 0. So cell A4 is on row 3.
1595
1596        If aggressive is True, empty cells with style are considered empty.
1597
1598        Arguments:
1599
1600            y -- int or str
1601
1602            aggressive -- bool
1603        """
1604        return self.get_row(y, clone=False).is_empty(aggressive=aggressive)

Return wether every cell in the row at the given "y" position has no value or the value evaluates to False (empty string), and no style.

Position start at 0. So cell A4 is on row 3.

If aggressive is True, empty cells with style are considered empty.

Arguments:

y -- int or str

aggressive -- bool
def get_cells( self, coord: tuple | list | str | None = None, cell_type: str | None = None, style: str | None = None, content: str | None = None, flat: bool = False) -> list:
1610    def get_cells(
1611        self,
1612        coord: tuple | list | str | None = None,
1613        cell_type: str | None = None,
1614        style: str | None = None,
1615        content: str | None = None,
1616        flat: bool = False,
1617    ) -> list:
1618        """Get the cells matching the criteria. If 'coord' is None,
1619        parse the whole table, else parse the area defined by 'coord'.
1620
1621        Filter by  cell_type = "all"  will retrieve cells of any
1622        type, aka non empty cells.
1623
1624        If flat is True (default is False), the method return a single list
1625        of all the values, else a list of lists of cells.
1626
1627        if cell_type, style and content are None, get_cells() will return
1628        the exact number of cells of the area, including empty cells.
1629
1630        Arguments:
1631
1632            coordinates -- str or tuple of int : coordinates of area
1633
1634            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1635                         'currency', 'percentage' or 'all'
1636
1637            content -- str regex
1638
1639            style -- str
1640
1641            flat -- boolean
1642
1643        Return: list of tuples
1644        """
1645        if coord:
1646            x, y, z, t = self._translate_table_coordinates(coord)
1647        else:
1648            x = y = z = t = None
1649        if flat:
1650            cells: list[Cell] = []
1651            for row in self.traverse(start=y, end=t):
1652                row_cells = row.get_cells(
1653                    coord=(x, z),
1654                    cell_type=cell_type,
1655                    style=style,
1656                    content=content,
1657                )
1658                cells.extend(row_cells)
1659            return cells
1660        else:
1661            lcells: list[list[Cell]] = []
1662            for row in self.traverse(start=y, end=t):
1663                row_cells = row.get_cells(
1664                    coord=(x, z),
1665                    cell_type=cell_type,
1666                    style=style,
1667                    content=content,
1668                )
1669                lcells.append(row_cells)
1670            return lcells

Get the cells matching the criteria. If 'coord' is None, parse the whole table, else parse the area defined by 'coord'.

Filter by cell_type = "all" will retrieve cells of any type, aka non empty cells.

If flat is True (default is False), the method return a single list of all the values, else a list of lists of cells.

if cell_type, style and content are None, get_cells() will return the exact number of cells of the area, including empty cells.

Arguments:

coordinates -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

flat -- boolean

Return: list of tuples

def get_cell( self, coord: tuple | list | str, clone: bool = True, keep_repeated: bool = True) -> Cell:
1672    def get_cell(
1673        self,
1674        coord: tuple | list | str,
1675        clone: bool = True,
1676        keep_repeated: bool = True,
1677    ) -> Cell:
1678        """Get the cell at the given coordinates.
1679
1680        They are either a 2-uplet of (x, y) starting from 0, or a
1681        human-readable position like "C4".
1682
1683        A copy is returned, use ``set_cell`` to push it back.
1684
1685        Arguments:
1686
1687            coord -- (int, int) or str
1688
1689        Return: Cell
1690        """
1691        x, y = self._translate_cell_coordinates(coord)
1692        if x is None:
1693            raise ValueError
1694        if y is None:
1695            raise ValueError
1696        # Outside the defined table
1697        if y >= self.height:
1698            cell = Cell()
1699        else:
1700            # Inside the defined table
1701            row = self._get_row2_base(y)
1702            if row is None:
1703                raise ValueError
1704            read_cell = row.get_cell(x, clone=clone)
1705            if read_cell is None:
1706                raise ValueError
1707            cell = read_cell
1708            if not keep_repeated:
1709                repeated = cell.repeated or 1
1710                if repeated >= 2:
1711                    cell.repeated = None
1712        cell.x = x
1713        cell.y = y
1714        return cell

Get the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

A copy is returned, use set_cell to push it back.

Arguments:

coord -- (int, int) or str

Return: Cell

def get_value(self, coord: tuple | list | str, get_type: bool = False) -> Any:
1716    def get_value(
1717        self,
1718        coord: tuple | list | str,
1719        get_type: bool = False,
1720    ) -> Any:
1721        """Shortcut to get the Python value of the cell at the given
1722        coordinates.
1723
1724        If get_type is True, returns the tuples (value, ODF type)
1725
1726        coord is either a 2-uplet of (x, y) starting from 0, or a
1727        human-readable position like "C4". If an Area is given, the upper
1728        left position is used as coord.
1729
1730        Arguments:
1731
1732            coord -- (int, int) or str : coordinate
1733
1734        Return: Python type
1735        """
1736        x, y = self._translate_cell_coordinates(coord)
1737        if x is None:
1738            raise ValueError
1739        if y is None:
1740            raise ValueError
1741        # Outside the defined table
1742        if y >= self.height:
1743            if get_type:
1744                return (None, None)
1745            return None
1746        else:
1747            # Inside the defined table
1748            row = self._get_row2_base(y)
1749            if row is None:
1750                raise ValueError
1751            cell = row._get_cell2_base(x)
1752            if cell is None:
1753                if get_type:
1754                    return (None, None)
1755                return None
1756            return cell.get_value(get_type=get_type)

Shortcut to get the Python value of the cell at the given coordinates.

If get_type is True, returns the tuples (value, ODF type)

coord is either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4". If an Area is given, the upper left position is used as coord.

Arguments:

coord -- (int, int) or str : coordinate

Return: Python type

def set_cell( self, coord: tuple | list | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1758    def set_cell(
1759        self,
1760        coord: tuple | list | str,
1761        cell: Cell | None = None,
1762        clone: bool = True,
1763    ) -> Cell:
1764        """Replace a cell of the table at the given coordinates.
1765
1766        They are either a 2-uplet of (x, y) starting from 0, or a
1767        human-readable position like "C4".
1768
1769        Arguments:
1770
1771            coord -- (int, int) or str : coordinate
1772
1773            cell -- Cell
1774
1775        return the cell, with x and y updated
1776        """
1777        if cell is None:
1778            cell = Cell()
1779            clone = False
1780        x, y = self._translate_cell_coordinates(coord)
1781        if x is None:
1782            raise ValueError
1783        if y is None:
1784            raise ValueError
1785        cell.x = x
1786        cell.y = y
1787        if y >= self.height:
1788            row = Row()
1789            cell_back = row.set_cell(x, cell, clone=clone)
1790            self.set_row(y, row, clone=False)
1791        else:
1792            row_read = self._get_row2_base(y)
1793            if row_read is None:
1794                raise ValueError
1795            row = row_read
1796            row.y = y
1797            repeated = row.repeated or 1
1798            if repeated > 1:
1799                row = row.clone
1800                row.repeated = None
1801                cell_back = row.set_cell(x, cell, clone=clone)
1802                self.set_row(y, row, clone=False)
1803            else:
1804                cell_back = row.set_cell(x, cell, clone=clone)
1805                # Update width if necessary, since we don't use set_row
1806                self._update_width(row)
1807        return cell_back

Replace a cell of the table at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Arguments:

coord -- (int, int) or str : coordinate

cell -- Cell

return the cell, with x and y updated

def set_cells( self, cells: list[list[Cell]] | list[tuple[Cell]], coord: tuple | list | str | None = None, clone: bool = True) -> None:
1809    def set_cells(
1810        self,
1811        cells: list[list[Cell]] | list[tuple[Cell]],
1812        coord: tuple | list | str | None = None,
1813        clone: bool = True,
1814    ) -> None:
1815        """Set the cells in the table, from the 'coord' position.
1816
1817        'coord' is the coordinate of the upper left cell to be modified by
1818        values. If 'coord' is None, default to the position (0,0) ("A1").
1819        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1820        area is used as coordinate.
1821
1822        The table is *not* cleared before the operation, to reset the table
1823        before setting cells, use table.clear().
1824
1825        A list of lists is expected, with as many lists as rows to be set, and
1826        as many cell in each sublist as cells to be setted in the row.
1827
1828        Arguments:
1829
1830            cells -- list of list of cells
1831
1832            coord -- tuple or str
1833
1834            values -- list of lists of python types
1835        """
1836        if coord:
1837            x, y = self._translate_cell_coordinates(coord)
1838        else:
1839            x = y = 0
1840        if y is None:
1841            y = 0
1842        if x is None:
1843            x = 0
1844        y -= 1
1845        for row_cells in cells:
1846            y += 1
1847            if not row_cells:
1848                continue
1849            row = self.get_row(y, clone=True)
1850            repeated = row.repeated or 1
1851            if repeated >= 2:
1852                row.repeated = None
1853            row.set_cells(row_cells, start=x, clone=clone)
1854            self.set_row(y, row, clone=False)
1855            self._update_width(row)

Set the cells in the table, from the 'coord' position.

'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.

The table is not cleared before the operation, to reset the table before setting cells, use table.clear().

A list of lists is expected, with as many lists as rows to be set, and as many cell in each sublist as cells to be setted in the row.

Arguments:

cells -- list of list of cells

coord -- tuple or str

values -- list of lists of python types
def set_value( self, coord: tuple | list | str, value: Any, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
1857    def set_value(
1858        self,
1859        coord: tuple | list | str,
1860        value: Any,
1861        cell_type: str | None = None,
1862        currency: str | None = None,
1863        style: str | None = None,
1864    ) -> None:
1865        """Set the Python value of the cell at the given coordinates.
1866
1867        They are either a 2-uplet of (x, y) starting from 0, or a
1868        human-readable position like "C4".
1869
1870        Arguments:
1871
1872            coord -- (int, int) or str
1873
1874            value -- Python type
1875
1876            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1877                     'string' or 'time'
1878
1879            currency -- three-letter str
1880
1881            style -- str
1882
1883        """
1884        self.set_cell(
1885            coord,
1886            Cell(value, cell_type=cell_type, currency=currency, style=style),
1887            clone=False,
1888        )

Set the Python value of the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Arguments:

coord -- (int, int) or str

value -- Python type

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
         'string' or 'time'

currency -- three-letter str

style -- str
def set_cell_image( self, coord: tuple | list | str, image_frame: Frame, doc_type: str | None = None) -> None:
1890    def set_cell_image(
1891        self,
1892        coord: tuple | list | str,
1893        image_frame: Frame,
1894        doc_type: str | None = None,
1895    ) -> None:
1896        """Do all the magic to display an image in the cell at the given
1897        coordinates.
1898
1899        They are either a 2-uplet of (x, y) starting from 0, or a
1900        human-readable position like "C4".
1901
1902        The frame element must contain the expected image position and
1903        dimensions.
1904
1905        DrawImage insertion depends on the document type, so the type must be
1906        provided or the table element must be already attached to a document.
1907
1908        Arguments:
1909
1910            coord -- (int, int) or str
1911
1912            image_frame -- Frame including an image
1913
1914            doc_type -- 'spreadsheet' or 'text'
1915        """
1916        # Test document type
1917        if doc_type is None:
1918            body = self.document_body
1919            if body is None:
1920                raise ValueError("document type not found")
1921            doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get(
1922                body.tag
1923            )
1924            if doc_type is None:
1925                raise ValueError("document type not supported for images")
1926        # We need the end address of the image
1927        x, y = self._translate_cell_coordinates(coord)
1928        if x is None:
1929            raise ValueError
1930        if y is None:
1931            raise ValueError
1932        cell = self.get_cell((x, y))
1933        image_frame = image_frame.clone  # type: ignore
1934        # Remove any previous paragraph, frame, etc.
1935        for child in cell.children:
1936            cell.delete(child)
1937        # Now it all depends on the document type
1938        if doc_type == "spreadsheet":
1939            image_frame.anchor_type = "char"
1940            # The frame needs end coordinates
1941            width, height = image_frame.size
1942            image_frame.set_attribute("table:end-x", width)
1943            image_frame.set_attribute("table:end-y", height)
1944            # FIXME what happens when the address changes?
1945            address = f"{self.name}.{digit_to_alpha(x)}{y + 1}"
1946            image_frame.set_attribute("table:end-cell-address", address)
1947            # The frame is directly in the cell
1948            cell.append(image_frame)
1949        elif doc_type == "text":
1950            # The frame must be in a paragraph
1951            cell.set_value("")
1952            paragraph = cell.get_element("text:p")
1953            if paragraph is None:
1954                raise ValueError
1955            paragraph.append(image_frame)
1956        self.set_cell(coord, cell)

Do all the magic to display an image in the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

The frame element must contain the expected image position and dimensions.

DrawImage insertion depends on the document type, so the type must be provided or the table element must be already attached to a document.

Arguments:

coord -- (int, int) or str

image_frame -- Frame including an image

doc_type -- 'spreadsheet' or 'text'
def insert_cell( self, coord: tuple | list | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1958    def insert_cell(
1959        self,
1960        coord: tuple | list | str,
1961        cell: Cell | None = None,
1962        clone: bool = True,
1963    ) -> Cell:
1964        """Insert the given cell at the given coordinates. If no cell is
1965        given, an empty one is created.
1966
1967        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
1968        human-readable position like "C4".
1969
1970        Cells on the right are shifted. Other rows remain untouched.
1971
1972        Arguments:
1973
1974            coord -- (int, int) or str
1975
1976            cell -- Cell
1977
1978        returns the cell with x and y updated
1979        """
1980        if cell is None:
1981            cell = Cell()
1982            clone = False
1983        if clone:
1984            cell = cell.clone
1985        x, y = self._translate_cell_coordinates(coord)
1986        if x is None:
1987            raise ValueError
1988        if y is None:
1989            raise ValueError
1990        row = self._get_row2(y, clone=True)
1991        row.y = y
1992        row.repeated = None
1993        cell_back = row.insert_cell(x, cell, clone=False)
1994        self.set_row(y, row, clone=False)
1995        # Update width if necessary
1996        self._update_width(row)
1997        return cell_back

Insert the given cell at the given coordinates. If no cell is given, an empty one is created.

Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Cells on the right are shifted. Other rows remain untouched.

Arguments:

coord -- (int, int) or str

cell -- Cell

returns the cell with x and y updated

def append_cell( self, y: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1999    def append_cell(
2000        self,
2001        y: int | str,
2002        cell: Cell | None = None,
2003        clone: bool = True,
2004    ) -> Cell:
2005        """Append the given cell at the "y" coordinate. Repeated cells are
2006        accepted. If no cell is given, an empty one is created.
2007
2008        Position start at 0. So cell A4 is on row 3.
2009
2010        Other rows remain untouched.
2011
2012        Arguments:
2013
2014            y -- int or str
2015
2016            cell -- Cell
2017
2018        returns the cell with x and y updated
2019        """
2020        if cell is None:
2021            cell = Cell()
2022            clone = False
2023        if clone:
2024            cell = cell.clone
2025        y = self._translate_y_from_any(y)
2026        row = self._get_row2(y)
2027        row.y = y
2028        cell_back = row.append_cell(cell, clone=False)
2029        self.set_row(y, row)
2030        # Update width if necessary
2031        self._update_width(row)
2032        return cell_back

Append the given cell at the "y" coordinate. Repeated cells are accepted. If no cell is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

Other rows remain untouched.

Arguments:

y -- int or str

cell -- Cell

returns the cell with x and y updated

def delete_cell(self, coord: tuple | list | str) -> None:
2034    def delete_cell(self, coord: tuple | list | str) -> None:
2035        """Delete the cell at the given coordinates, so that next cells are
2036        shifted to the left.
2037
2038        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2039        human-readable position like "C4".
2040
2041        Use set_value() for erasing value.
2042
2043        Arguments:
2044
2045            coord -- (int, int) or str
2046        """
2047        x, y = self._translate_cell_coordinates(coord)
2048        if x is None:
2049            raise ValueError
2050        if y is None:
2051            raise ValueError
2052        # Outside the defined table
2053        if y >= self.height:
2054            return
2055        # Inside the defined table
2056        row = self._get_row2_base(y)
2057        if row is None:
2058            raise ValueError
2059        row.delete_cell(x)
2060        # self.set_row(y, row)

Delete the cell at the given coordinates, so that next cells are shifted to the left.

Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Use set_value() for erasing value.

Arguments:

coord -- (int, int) or str
def traverse_columns( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Column]:
2067    def traverse_columns(  # noqa: C901
2068        self,
2069        start: int | None = None,
2070        end: int | None = None,
2071    ) -> Iterator[Column]:
2072        """Yield as many column elements as expected columns in the table,
2073        i.e. expand repetitions by returning the same column as many times as
2074        necessary.
2075
2076            Arguments:
2077
2078                start -- int
2079
2080                end -- int
2081
2082        Copies are returned, use set_column() to push them back.
2083        """
2084        idx = -1
2085        before = -1
2086        x = 0
2087        if start is None and end is None:
2088            for juska in self._cmap:
2089                idx += 1
2090                if idx in self._indexes["_cmap"]:
2091                    column = self._indexes["_cmap"][idx]
2092                else:
2093                    column = self._get_element_idx2(_xpath_column_idx, idx)
2094                    self._indexes["_cmap"][idx] = column
2095                repeated = juska - before
2096                before = juska
2097                for _i in range(repeated or 1):
2098                    # Return a copy without the now obsolete repetition
2099                    column = column.clone
2100                    column.x = x
2101                    x += 1
2102                    if repeated > 1:
2103                        column.repeated = None
2104                    yield column
2105        else:
2106            if start is None:
2107                start = 0
2108            start = max(0, start)
2109            if end is None:
2110                try:
2111                    end = self._cmap[-1]
2112                except Exception:
2113                    end = -1
2114            start_map = find_odf_idx(self._cmap, start)
2115            if start_map is None:
2116                return
2117            if start_map > 0:
2118                before = self._cmap[start_map - 1]
2119            idx = start_map - 1
2120            before = start - 1
2121            x = start
2122            for juska in self._cmap[start_map:]:
2123                idx += 1
2124                if idx in self._indexes["_cmap"]:
2125                    column = self._indexes["_cmap"][idx]
2126                else:
2127                    column = self._get_element_idx2(_xpath_column_idx, idx)
2128                    self._indexes["_cmap"][idx] = column
2129                repeated = juska - before
2130                before = juska
2131                for _i in range(repeated or 1):
2132                    if x <= end:
2133                        column = column.clone
2134                        column.x = x
2135                        x += 1
2136                        if repeated > 1 or (x == start and start > 0):
2137                            column.repeated = None
2138                        yield column

Yield as many column elements as expected columns in the table, i.e. expand repetitions by returning the same column as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_column() to push them back.

def get_columns( self, coord: tuple | list | str | None = None, style: str | None = None) -> list[Column]:
2140    def get_columns(
2141        self,
2142        coord: tuple | list | str | None = None,
2143        style: str | None = None,
2144    ) -> list[Column]:
2145        """Get the list of columns matching the criteria. Each result is a
2146        tuple of (x, column).
2147
2148        Arguments:
2149
2150            coord -- str or tuple of int : coordinates of columns
2151
2152            style -- str
2153
2154        Return: list of columns
2155        """
2156        if coord:
2157            x, _y, _z, t = self._translate_column_coordinates(coord)
2158        else:
2159            x = t = None
2160        if not style:
2161            return list(self.traverse_columns(start=x, end=t))
2162        columns = []
2163        for column in self.traverse_columns(start=x, end=t):
2164            if style != column.style:
2165                continue
2166            columns.append(column)
2167        return columns

Get the list of columns matching the criteria. Each result is a tuple of (x, column).

Arguments:

coord -- str or tuple of int : coordinates of columns

style -- str

Return: list of columns

def get_column(self, x: int | str) -> Column:
2184    def get_column(self, x: int | str) -> Column:
2185        """Get the column at the given "x" position.
2186
2187        ODF columns don't contain cells, only style information.
2188
2189        Position start at 0. So cell C4 is on column 2. Alphabetical position
2190        like "C" is accepted.
2191
2192        A copy is returned, use set_column() to push it back.
2193
2194        Arguments:
2195
2196            x -- int or str
2197
2198        Return: Column
2199        """
2200        x = self._translate_x_from_any(x)
2201        column = self._get_column2(x)
2202        if column is None:
2203            raise ValueError
2204        column.x = x
2205        return column

Get the column at the given "x" position.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

A copy is returned, use set_column() to push it back.

Arguments:

x -- int or str

Return: Column

def set_column( self, x: int | str, column: Column | None = None) -> Column:
2207    def set_column(
2208        self,
2209        x: int | str,
2210        column: Column | None = None,
2211    ) -> Column:
2212        """Replace the column at the given "x" position.
2213
2214        ODF columns don't contain cells, only style information.
2215
2216        Position start at 0. So cell C4 is on column 2. Alphabetical position
2217        like "C" is accepted.
2218
2219        Arguments:
2220
2221            x -- int or str
2222
2223            column -- Column
2224        """
2225        x = self._translate_x_from_any(x)
2226        if column is None:
2227            column = Column()
2228            repeated = 1
2229        else:
2230            repeated = column.repeated or 1
2231        column.x = x
2232        # Outside the defined table ?
2233        diff = x - self.width
2234        if diff == 0:
2235            column_back = self.append_column(column, _repeated=repeated)
2236        elif diff > 0:
2237            self.append_column(Column(repeated=diff), _repeated=diff)
2238            column_back = self.append_column(column, _repeated=repeated)
2239        else:
2240            # Inside the defined table
2241            column_back = set_item_in_vault(  # type: ignore
2242                x, column, self, _xpath_column_idx, "_cmap"
2243            )
2244        return column_back

Replace the column at the given "x" position.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str

column -- Column
def insert_column( self, x: int | str, column: Column | None = None) -> Column:
2246    def insert_column(
2247        self,
2248        x: int | str,
2249        column: Column | None = None,
2250    ) -> Column:
2251        """Insert the column before the given "x" position. If no column is
2252        given, an empty one is created.
2253
2254        ODF columns don't contain cells, only style information.
2255
2256        Position start at 0. So cell C4 is on column 2. Alphabetical position
2257        like "C" is accepted.
2258
2259        Arguments:
2260
2261            x -- int or str
2262
2263            column -- Column
2264        """
2265        if column is None:
2266            column = Column()
2267        x = self._translate_x_from_any(x)
2268        diff = x - self.width
2269        if diff < 0:
2270            column_back = insert_item_in_vault(
2271                x, column, self, _xpath_column_idx, "_cmap"
2272            )
2273        elif diff == 0:
2274            column_back = self.append_column(column.clone)
2275        else:
2276            self.append_column(Column(repeated=diff), _repeated=diff)
2277            column_back = self.append_column(column.clone)
2278        column_back.x = x  # type: ignore
2279        # Repetitions are accepted
2280        repeated = column.repeated or 1
2281        # Update width on every row
2282        for row in self._get_rows():
2283            if row.width > x:
2284                row.insert_cell(x, Cell(repeated=repeated))
2285            # Shorter rows don't need insert
2286            # Longer rows shouldn't exist!
2287        return column_back  # type: ignore

Insert the column before the given "x" position. If no column is given, an empty one is created.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str

column -- Column
def append_column( self, column: Column | None = None, _repeated: int | None = None) -> Column:
2289    def append_column(
2290        self,
2291        column: Column | None = None,
2292        _repeated: int | None = None,
2293    ) -> Column:
2294        """Append the column at the end of the table. If no column is given,
2295        an empty one is created.
2296
2297        ODF columns don't contain cells, only style information.
2298
2299        Position start at 0. So cell C4 is on column 2. Alphabetical position
2300        like "C" is accepted.
2301
2302        Arguments:
2303
2304            column -- Column
2305        """
2306        if column is None:
2307            column = Column()
2308        else:
2309            column = column.clone
2310        if not self._cmap:
2311            position = 0
2312        else:
2313            odf_idx = len(self._cmap) - 1
2314            last_column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2315            if last_column is None:
2316                raise ValueError
2317            position = self.index(last_column) + 1
2318        column.x = self.width
2319        self.insert(column, position=position)
2320        # Repetitions are accepted
2321        if _repeated is None:
2322            _repeated = column.repeated or 1
2323        self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated)
2324        # No need to update row widths
2325        return column

Append the column at the end of the table. If no column is given, an empty one is created.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

column -- Column
def delete_column(self, x: int | str) -> None:
2327    def delete_column(self, x: int | str) -> None:
2328        """Delete the column at the given position. ODF columns don't contain
2329        cells, only style information.
2330
2331        Position start at 0. So cell C4 is on column 2. Alphabetical position
2332        like "C" is accepted.
2333
2334        Arguments:
2335
2336            x -- int or str
2337        """
2338        x = self._translate_x_from_any(x)
2339        # Outside the defined table
2340        if x >= self.width:
2341            return
2342        # Inside the defined table
2343        delete_item_in_vault(x, self, _xpath_column_idx, "_cmap")
2344        # Update width
2345        width = self.width
2346        for row in self._get_rows():
2347            if row.width >= width:
2348                row.delete_cell(x)

Delete the column at the given position. ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str
def get_column_cells( self, x: int | str, style: str | None = None, content: str | None = None, cell_type: str | None = None, complete: bool = False) -> list[Cell | None]:
2350    def get_column_cells(  # noqa: C901
2351        self,
2352        x: int | str,
2353        style: str | None = None,
2354        content: str | None = None,
2355        cell_type: str | None = None,
2356        complete: bool = False,
2357    ) -> list[Cell | None]:
2358        """Get the list of cells at the given position.
2359
2360        Position start at 0. So cell C4 is on column 2. Alphabetical position
2361        like "C" is accepted.
2362
2363        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2364        type, aka non empty cells.
2365
2366        If complete is True, replace missing values by None.
2367
2368        Arguments:
2369
2370            x -- int or str
2371
2372            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2373                         'currency', 'percentage' or 'all'
2374
2375            content -- str regex
2376
2377            style -- str
2378
2379            complete -- boolean
2380
2381        Return: list of Cell
2382        """
2383        x = self._translate_x_from_any(x)
2384        if cell_type:
2385            cell_type = cell_type.lower().strip()
2386        cells: list[Cell | None] = []
2387        if not style and not content and not cell_type:
2388            for row in self.traverse():
2389                cells.append(row.get_cell(x, clone=True))
2390            return cells
2391        for row in self.traverse():
2392            cell = row.get_cell(x, clone=True)
2393            if cell is None:
2394                raise ValueError
2395            # Filter the cells by cell_type
2396            if cell_type:
2397                ctype = cell.type
2398                if not ctype or not (ctype == cell_type or cell_type == "all"):
2399                    if complete:
2400                        cells.append(None)
2401                    continue
2402            # Filter the cells with the regex
2403            if content and not cell.match(content):
2404                if complete:
2405                    cells.append(None)
2406                continue
2407            # Filter the cells with the style
2408            if style and style != cell.style:
2409                if complete:
2410                    cells.append(None)
2411                continue
2412            cells.append(cell)
2413        return cells

Get the list of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.

If complete is True, replace missing values by None.

Arguments:

x -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

complete -- boolean

Return: list of Cell

def get_column_values( self, x: int | str, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> list[typing.Any]:
2415    def get_column_values(
2416        self,
2417        x: int | str,
2418        cell_type: str | None = None,
2419        complete: bool = True,
2420        get_type: bool = False,
2421    ) -> list[Any]:
2422        """Shortcut to get the list of Python values for the cells at the
2423        given position.
2424
2425        Position start at 0. So cell C4 is on column 2. Alphabetical position
2426        like "C" is accepted.
2427
2428        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2429        type, aka non empty cells.
2430        If cell_type and complete is True, replace missing values by None.
2431
2432        If get_type is True, returns a tuple (value, ODF type of value)
2433
2434        Arguments:
2435
2436            x -- int or str
2437
2438            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2439                         'currency', 'percentage' or 'all'
2440
2441            complete -- boolean
2442
2443            get_type -- boolean
2444
2445        Return: list of Python types
2446        """
2447        cells = self.get_column_cells(
2448            x, style=None, content=None, cell_type=cell_type, complete=complete
2449        )
2450        values: list[Any] = []
2451        for cell in cells:
2452            if cell is None:
2453                if complete:
2454                    if get_type:
2455                        values.append((None, None))
2456                    else:
2457                        values.append(None)
2458                continue
2459            if cell_type:
2460                ctype = cell.type
2461                if not ctype or not (ctype == cell_type or cell_type == "all"):
2462                    if complete:
2463                        if get_type:
2464                            values.append((None, None))
2465                        else:
2466                            values.append(None)
2467                    continue
2468            values.append(cell.get_value(get_type=get_type))
2469        return values

Shortcut to get the list of Python values for the cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.

If get_type is True, returns a tuple (value, ODF type of value)

Arguments:

x -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of Python types

def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2471    def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2472        """Shortcut to set the list of cells at the given position.
2473
2474        Position start at 0. So cell C4 is on column 2. Alphabetical position
2475        like "C" is accepted.
2476
2477        The list must have the same length than the table height.
2478
2479        Arguments:
2480
2481            x -- int or str
2482
2483            cells -- list of Cell
2484        """
2485        height = self.height
2486        if len(cells) != height:
2487            raise ValueError(f"col mismatch: {height} cells expected")
2488        cells_iterator = iter(cells)
2489        for y, row in enumerate(self.traverse()):
2490            row.set_cell(x, next(cells_iterator))
2491            self.set_row(y, row)

Shortcut to set the list of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

The list must have the same length than the table height.

Arguments:

x -- int or str

cells -- list of Cell
def set_column_values( self, x: int | str, values: list, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
2493    def set_column_values(
2494        self,
2495        x: int | str,
2496        values: list,
2497        cell_type: str | None = None,
2498        currency: str | None = None,
2499        style: str | None = None,
2500    ) -> None:
2501        """Shortcut to set the list of Python values of cells at the given
2502        position.
2503
2504        Position start at 0. So cell C4 is on column 2. Alphabetical position
2505        like "C" is accepted.
2506
2507        The list must have the same length than the table height.
2508
2509        Arguments:
2510
2511            x -- int or str
2512
2513            values -- list of Python types
2514
2515            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
2516                         'string' or 'time'
2517
2518            currency -- three-letter str
2519
2520            style -- str
2521        """
2522        cells = [
2523            Cell(value, cell_type=cell_type, currency=currency, style=style)
2524            for value in values
2525        ]
2526        self.set_column_cells(x, cells)

Shortcut to set the list of Python values of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

The list must have the same length than the table height.

Arguments:

x -- int or str

values -- list of Python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str
def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2528    def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2529        """Return wether every cell in the column at "x" position has no value
2530        or the value evaluates to False (empty string), and no style.
2531
2532        Position start at 0. So cell C4 is on column 2. Alphabetical position
2533        like "C" is accepted.
2534
2535        If aggressive is True, empty cells with style are considered empty.
2536
2537        Return: bool
2538        """
2539        for cell in self.get_column_cells(x):
2540            if cell is None:
2541                continue
2542            if not cell.is_empty(aggressive=aggressive):
2543                return False
2544        return True

Return wether every cell in the column at "x" position has no value or the value evaluates to False (empty string), and no style.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

If aggressive is True, empty cells with style are considered empty.

Return: bool

def get_named_ranges( self, table_name: str | list[str] | None = None) -> list[NamedRange]:
2548    def get_named_ranges(  # type: ignore
2549        self,
2550        table_name: str | list[str] | None = None,
2551    ) -> list[NamedRange]:
2552        """Returns the list of available Name Ranges of the spreadsheet. If
2553        table_name is provided, limits the search to these tables.
2554        Beware : named ranges are stored at the body level, thus do not call
2555        this method on a cloned table.
2556
2557        Arguments:
2558
2559            table_names -- str or list of str, names of tables
2560
2561        Return : list of table_range
2562        """
2563        body = self.document_body
2564        if not body:
2565            return []
2566        all_named_ranges = body.get_named_ranges()
2567        if not table_name:
2568            return all_named_ranges  # type:ignore
2569        filter_ = []
2570        if isinstance(table_name, str):
2571            filter_.append(table_name)
2572        elif isiterable(table_name):
2573            filter_.extend(table_name)
2574        else:
2575            raise ValueError(
2576                f"table_name must be string or Iterable, not {type(table_name)}"
2577            )
2578        return [
2579            nr for nr in all_named_ranges if nr.table_name in filter_  # type:ignore
2580        ]

Returns the list of available Name Ranges of the spreadsheet. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

table_names -- str or list of str, names of tables

Return : list of table_range

def get_named_range(self, name: str) -> NamedRange:
2582    def get_named_range(self, name: str) -> NamedRange:
2583        """Returns the Name Ranges of the specified name. If
2584        table_name is provided, limits the search to these tables.
2585        Beware : named ranges are stored at the body level, thus do not call
2586        this method on a cloned table.
2587
2588        Arguments:
2589
2590            name -- str, name of the named range object
2591
2592        Return : NamedRange
2593        """
2594        body = self.document_body
2595        if not body:
2596            raise ValueError("Table is not inside a document")
2597        return body.get_named_range(name)  # type: ignore

Returns the Name Ranges of the specified name. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str, name of the named range object

Return : NamedRange

def set_named_range( self, name: str, crange: str | tuple | list, table_name: str | None = None, usage: str | None = None) -> None:
2599    def set_named_range(
2600        self,
2601        name: str,
2602        crange: str | tuple | list,
2603        table_name: str | None = None,
2604        usage: str | None = None,
2605    ) -> None:
2606        """Create a Named Range element and insert it in the document.
2607        Beware : named ranges are stored at the body level, thus do not call
2608        this method on a cloned table.
2609
2610        Arguments:
2611
2612            name -- str, name of the named range
2613
2614            crange -- str or tuple of int, cell or area coordinate
2615
2616            table_name -- str, name of the table
2617
2618            uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2619        """
2620        body = self.document_body
2621        if not body:
2622            raise ValueError("Table is not inside a document")
2623        if not name:
2624            raise ValueError("Name required.")
2625        if table_name is None:
2626            table_name = self.name
2627        named_range = NamedRange(name, crange, table_name, usage)
2628        body.append_named_range(named_range)

Create a Named Range element and insert it in the document. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str, name of the named range

crange -- str or tuple of int, cell or area coordinate

table_name -- str, name of the table

uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
def delete_named_range(self, name: str) -> None:
2630    def delete_named_range(self, name: str) -> None:
2631        """Delete the Named Range of specified name from the spreadsheet.
2632        Beware : named ranges are stored at the body level, thus do not call
2633        this method on a cloned table.
2634
2635        Arguments:
2636
2637            name -- str
2638        """
2639        name = name.strip()
2640        if not name:
2641            raise ValueError("Name required.")
2642        body = self.document_body
2643        if not body:
2644            raise ValueError("Table is not inside a document.")
2645        body.delete_named_range(name)

Delete the Named Range of specified name from the spreadsheet. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str
def set_span(self, area: str | tuple | list, merge: bool = False) -> bool:
2651    def set_span(  # noqa: C901
2652        self,
2653        area: str | tuple | list,
2654        merge: bool = False,
2655    ) -> bool:
2656        """Create a Cell Span : span the first cell of the area on several
2657        columns and/or rows.
2658        If merge is True, replace text of the cell by the concatenation of
2659        existing text in covered cells.
2660        Beware : if merge is True, old text is changed, if merge is False
2661        (the default), old text in coverd cells is still present but not
2662        displayed by most GUI.
2663
2664        If the area defines only one cell, the set span will do nothing.
2665        It is not allowed to apply set span to an area whose one cell already
2666        belongs to previous cell span.
2667
2668        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2669        be provided as an alpha numeric value like "A1:B2' or a tuple like
2670        (0, 0, 1, 1) or (0, 0).
2671
2672        Arguments:
2673
2674            area -- str or tuple of int, cell or area coordinate
2675
2676            merge -- boolean
2677        """
2678        # get area
2679        digits = convert_coordinates(area)
2680        if len(digits) == 4:
2681            x, y, z, t = digits
2682        else:
2683            x, y = digits
2684            z, t = digits
2685        start = x, y
2686        end = z, t
2687        if start == end:
2688            # one cell : do nothing
2689            return False
2690        if x is None:
2691            raise ValueError
2692        if y is None:
2693            raise ValueError
2694        if z is None:
2695            raise ValueError
2696        if t is None:
2697            raise ValueError
2698        # check for previous span
2699        good = True
2700        # Check boundaries and empty cells : need to crate non existent cells
2701        # so don't use get_cells directly, but get_cell
2702        cells = []
2703        for yy in range(y, t + 1):
2704            row_cells = []
2705            for xx in range(x, z + 1):
2706                row_cells.append(
2707                    self.get_cell((xx, yy), clone=True, keep_repeated=False)
2708                )
2709            cells.append(row_cells)
2710        for row in cells:
2711            for cell in row:
2712                if cell._is_spanned():
2713                    good = False
2714                    break
2715            if not good:
2716                break
2717        if not good:
2718            return False
2719        # Check boundaries
2720        # if z >= self.width or t >= self.height:
2721        #    self.set_cell(coord = end)
2722        #    print area, z, t
2723        #    cells = self.get_cells((x, y, z, t))
2724        #    print cells
2725        # do it:
2726        if merge:
2727            val_list = []
2728            for row in cells:
2729                for cell in row:
2730                    if cell.is_empty(aggressive=True):
2731                        continue
2732                    val = cell.get_value()
2733                    if val is not None:
2734                        if isinstance(val, str):
2735                            val.strip()
2736                        if val != "":
2737                            val_list.append(val)
2738                        cell.clear()
2739            if val_list:
2740                if len(val_list) == 1:
2741                    cells[0][0].set_value(val_list[0])
2742                else:
2743                    value = " ".join([str(v) for v in val_list if v])
2744                    cells[0][0].set_value(value)
2745        cols = z - x + 1
2746        cells[0][0].set_attribute("table:number-columns-spanned", str(cols))
2747        rows = t - y + 1
2748        cells[0][0].set_attribute("table:number-rows-spanned", str(rows))
2749        for cell in cells[0][1:]:
2750            cell.tag = "table:covered-table-cell"
2751        for row in cells[1:]:
2752            for cell in row:
2753                cell.tag = "table:covered-table-cell"
2754        # replace cells in table
2755        self.set_cells(cells, coord=start, clone=False)
2756        return True

Create a Cell Span : span the first cell of the area on several columns and/or rows. If merge is True, replace text of the cell by the concatenation of existing text in covered cells. Beware : if merge is True, old text is changed, if merge is False (the default), old text in coverd cells is still present but not displayed by most GUI.

If the area defines only one cell, the set span will do nothing. It is not allowed to apply set span to an area whose one cell already belongs to previous cell span.

Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).

Arguments:

area -- str or tuple of int, cell or area coordinate

merge -- boolean
def del_span(self, area: str | tuple | list) -> bool:
2758    def del_span(self, area: str | tuple | list) -> bool:
2759        """Delete a Cell Span. 'area' is the cell coordiante of the upper left
2760        cell of the spanned area.
2761
2762        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2763        be provided as an alpha numeric value like "A1:B2' or a tuple like
2764        (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell
2765        is used.
2766
2767        Arguments:
2768
2769            area -- str or tuple of int, cell or area coordinate
2770        """
2771        # get area
2772        digits = convert_coordinates(area)
2773        if len(digits) == 4:
2774            x, y, _z, _t = digits
2775        else:
2776            x, y = digits
2777        if x is None:
2778            raise ValueError
2779        if y is None:
2780            raise ValueError
2781        start = x, y
2782        # check for previous span
2783        cell0 = self.get_cell(start)
2784        nb_cols = cell0.get_attribute_integer("table:number-columns-spanned")
2785        if nb_cols is None:
2786            return False
2787        nb_rows = cell0.get_attribute_integer("table:number-rows-spanned")
2788        if nb_rows is None:
2789            return False
2790        z = x + nb_cols - 1
2791        t = y + nb_rows - 1
2792        cells = self.get_cells((x, y, z, t))
2793        cells[0][0].del_attribute("table:number-columns-spanned")
2794        cells[0][0].del_attribute("table:number-rows-spanned")
2795        for cell in cells[0][1:]:
2796            cell.tag = "table:table-cell"
2797        for row in cells[1:]:
2798            for cell in row:
2799                cell.tag = "table:table-cell"
2800        # replace cells in table
2801        self.set_cells(cells, coord=start, clone=False)
2802        return True

Delete a Cell Span. 'area' is the cell coordiante of the upper left cell of the spanned area.

Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell is used.

Arguments:

area -- str or tuple of int, cell or area coordinate
def to_csv( self, path_or_file: str | pathlib.Path | None = None, dialect: str = 'excel') -> Any:
2806    def to_csv(
2807        self,
2808        path_or_file: str | Path | None = None,
2809        dialect: str = "excel",
2810    ) -> Any:
2811        """Write the table as CSV in the file.
2812
2813        If the file is a string, it is opened as a local path. Else an
2814        opened file-like is expected.
2815
2816        Arguments:
2817
2818            path_or_file -- str or file-like
2819
2820            dialect -- str, python csv.dialect, can be 'excel', 'unix'...
2821        """
2822
2823        def write_content(csv_writer: object) -> None:
2824            for values in self.iter_values():
2825                line = []
2826                for value in values:
2827                    if value is None:
2828                        value = ""
2829                    if isinstance(value, str):
2830                        value = value.strip()
2831                    line.append(value)
2832                csv_writer.writerow(line)  # type: ignore
2833
2834        out = StringIO(newline="")
2835        csv_writer = csv.writer(out, dialect=dialect)
2836        write_content(csv_writer)
2837        if path_or_file is None:
2838            return out.getvalue()
2839        path = Path(path_or_file)
2840        path.write_text(out.getvalue())
2841        return None

Write the table as CSV in the file.

If the file is a string, it is opened as a local path. Else an opened file-like is expected.

Arguments:

path_or_file -- str or file-like

dialect -- str, python csv.dialect, can be 'excel', 'unix'...
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
append_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Text(builtins.str):
275class Text(str):
276    """Representation of an XML text node. Created to hide the specifics of
277    lxml in searching text nodes using XPath.
278
279    Constructed like any str object but only accepts lxml text objects.
280    """
281
282    # There's some black magic in inheriting from str
283    def __init__(
284        self,
285        text_result: _ElementUnicodeResult | _ElementStringResult,
286    ) -> None:
287        self.__parent = text_result.getparent()
288        self.__is_text = text_result.is_text
289        self.__is_tail = text_result.is_tail
290
291    @property
292    def parent(self) -> Element | None:
293        parent = self.__parent
294        # XXX happens just because of the unit test
295        if parent is None:
296            return None
297        return Element.from_tag(tag_or_elem=parent)
298
299    def is_text(self) -> bool:
300        return self.__is_text
301
302    def is_tail(self) -> bool:
303        return self.__is_tail

Representation of an XML text node. Created to hide the specifics of lxml in searching text nodes using XPath.

Constructed like any str object but only accepts lxml text objects.

Text( text_result: lxml.etree._ElementUnicodeResult | lxml.etree._ElementStringResult)
283    def __init__(
284        self,
285        text_result: _ElementUnicodeResult | _ElementStringResult,
286    ) -> None:
287        self.__parent = text_result.getparent()
288        self.__is_text = text_result.is_text
289        self.__is_tail = text_result.is_tail
parent: Element | None
291    @property
292    def parent(self) -> Element | None:
293        parent = self.__parent
294        # XXX happens just because of the unit test
295        if parent is None:
296            return None
297        return Element.from_tag(tag_or_elem=parent)
def is_text(self) -> bool:
299    def is_text(self) -> bool:
300        return self.__is_text
def is_tail(self) -> bool:
302    def is_tail(self) -> bool:
303        return self.__is_tail
Inherited Members
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
removeprefix
removesuffix
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class TextChange(odfdo.Element):
508class TextChange(Element):
509    """The TextChange "text:change" element marks a position in an empty
510    region where text has been deleted.
511    """
512
513    _tag = "text:change"
514
515    def get_id(self) -> str | None:
516        return self.get_attribute_string("text:change-id")
517
518    def set_id(self, idx: str) -> None:
519        self.set_attribute("text:change-id", idx)
520
521    def _get_tracked_changes(self) -> Element | None:
522        body = self.document_body
523        if not body:
524            raise ValueError
525        return body.get_tracked_changes()
526
527    def get_changed_region(
528        self,
529        tracked_changes: Element | None = None,
530    ) -> Element | None:
531        if not tracked_changes:
532            tracked_changes = self._get_tracked_changes()
533        idx = self.get_id()
534        return tracked_changes.get_changed_region(text_id=idx)  # type: ignore
535
536    def get_change_info(
537        self,
538        tracked_changes: Element | None = None,
539    ) -> Element | None:
540        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
541        if not changed_region:
542            return None
543        return changed_region.get_change_info()  # type: ignore
544
545    def get_change_element(
546        self,
547        tracked_changes: Element | None = None,
548    ) -> Element | None:
549        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
550        if not changed_region:
551            return None
552        return changed_region.get_change_element()  # type: ignore
553
554    def get_deleted(
555        self,
556        tracked_changes: Element | None = None,
557        as_text: bool = False,
558        no_header: bool = False,
559        clean: bool = True,
560    ) -> Element | None:
561        """Shortcut to get the deleted informations stored in the
562        TextDeletion stored in the tracked changes.
563
564        Return: Paragraph (or None)."
565        """
566        changed = self.get_change_element(tracked_changes=tracked_changes)
567        if not changed:
568            return None
569        return changed.get_deleted(  # type: ignore
570            as_text=as_text,
571            no_header=no_header,
572            clean=clean,
573        )
574
575    def get_inserted(
576        self,
577        as_text: bool = False,
578        no_header: bool = False,
579        clean: bool = True,
580    ) -> str | Element | list[Element] | None:
581        """Return None."""
582        return None
583
584    def get_start(self) -> TextChangeStart | None:
585        """Return None."""
586        return None
587
588    def get_end(self) -> TextChangeEnd | None:
589        """Return None."""
590        return None

The TextChange "text:change" element marks a position in an empty region where text has been deleted.

def get_id(self) -> str | None:
515    def get_id(self) -> str | None:
516        return self.get_attribute_string("text:change-id")
def set_id(self, idx: str) -> None:
518    def set_id(self, idx: str) -> None:
519        self.set_attribute("text:change-id", idx)
def get_changed_region( self, tracked_changes: Element | None = None) -> Element | None:
527    def get_changed_region(
528        self,
529        tracked_changes: Element | None = None,
530    ) -> Element | None:
531        if not tracked_changes:
532            tracked_changes = self._get_tracked_changes()
533        idx = self.get_id()
534        return tracked_changes.get_changed_region(text_id=idx)  # type: ignore
def get_change_info( self, tracked_changes: Element | None = None) -> Element | None:
536    def get_change_info(
537        self,
538        tracked_changes: Element | None = None,
539    ) -> Element | None:
540        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
541        if not changed_region:
542            return None
543        return changed_region.get_change_info()  # type: ignore
def get_change_element( self, tracked_changes: Element | None = None) -> Element | None:
545    def get_change_element(
546        self,
547        tracked_changes: Element | None = None,
548    ) -> Element | None:
549        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
550        if not changed_region:
551            return None
552        return changed_region.get_change_element()  # type: ignore
def get_deleted( self, tracked_changes: Element | None = None, as_text: bool = False, no_header: bool = False, clean: bool = True) -> Element | None:
554    def get_deleted(
555        self,
556        tracked_changes: Element | None = None,
557        as_text: bool = False,
558        no_header: bool = False,
559        clean: bool = True,
560    ) -> Element | None:
561        """Shortcut to get the deleted informations stored in the
562        TextDeletion stored in the tracked changes.
563
564        Return: Paragraph (or None)."
565        """
566        changed = self.get_change_element(tracked_changes=tracked_changes)
567        if not changed:
568            return None
569        return changed.get_deleted(  # type: ignore
570            as_text=as_text,
571            no_header=no_header,
572            clean=clean,
573        )

Shortcut to get the deleted informations stored in the TextDeletion stored in the tracked changes.

Return: Paragraph (or None)."

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
575    def get_inserted(
576        self,
577        as_text: bool = False,
578        no_header: bool = False,
579        clean: bool = True,
580    ) -> str | Element | list[Element] | None:
581        """Return None."""
582        return None

Return None.

def get_start(self) -> TextChangeStart | None:
584    def get_start(self) -> TextChangeStart | None:
585        """Return None."""
586        return None

Return None.

def get_end(self) -> TextChangeEnd | None:
588    def get_end(self) -> TextChangeEnd | None:
589        """Return None."""
590        return None

Return None.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TextChangeEnd(odfdo.TextChange):
593class TextChangeEnd(TextChange):
594    """The TextChangeEnd "text:change-end" element marks the end of a region
595    with content where text has been inserted or the format has been
596    changed.
597    """
598
599    _tag = "text:change-end"
600
601    def get_start(self) -> TextChangeStart | None:
602        """Return the corresponding annotation starting tag or None."""
603        idx = self.get_id()
604        parent = self.parent
605        if parent is None:
606            raise ValueError("Can not find end tag: no parent available.")
607        body = self.document_body
608        if not body:
609            body = self.root
610        return body.get_text_change_start(idx=idx)  # type: ignore
611
612    def get_end(self) -> TextChangeEnd | None:
613        """Return self."""
614        return self
615
616    def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
617        """Return None."""
618        return None
619
620    def get_inserted(
621        self,
622        as_text: bool = False,
623        no_header: bool = False,
624        clean: bool = True,
625    ) -> str | Element | list[Element] | None:
626        """Return the content between text:change-start and text:change-end.
627
628        If no content exists (deletion tag), returns None (or '' if text flag
629        is True).
630        If as_text is True: returns the text content.
631        If clean is True: suppress unwanted tags (deletions marks, ...)
632        If no_header is True: existing text:h are changed in text:p
633        By default: returns a list of Element, cleaned and with headers
634
635        Arguments:
636
637            as_text -- boolean
638
639            clean -- boolean
640
641            no_header -- boolean
642
643        Return: list or Element or text
644        """
645
646        # idx = self.get_id()
647        start = self.get_start()
648        end = self.get_end()
649        if end is None or start is None:
650            if as_text:
651                return ""
652            return None
653        body = self.document_body
654        if not body:
655            body = self.root
656        return body.get_between(
657            start, end, as_text=as_text, no_header=no_header, clean=clean
658        )

The TextChangeEnd "text:change-end" element marks the end of a region with content where text has been inserted or the format has been changed.

def get_start(self) -> TextChangeStart | None:
601    def get_start(self) -> TextChangeStart | None:
602        """Return the corresponding annotation starting tag or None."""
603        idx = self.get_id()
604        parent = self.parent
605        if parent is None:
606            raise ValueError("Can not find end tag: no parent available.")
607        body = self.document_body
608        if not body:
609            body = self.root
610        return body.get_text_change_start(idx=idx)  # type: ignore

Return the corresponding annotation starting tag or None.

def get_end(self) -> TextChangeEnd | None:
612    def get_end(self) -> TextChangeEnd | None:
613        """Return self."""
614        return self

Return self.

def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
616    def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
617        """Return None."""
618        return None

Return None.

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
620    def get_inserted(
621        self,
622        as_text: bool = False,
623        no_header: bool = False,
624        clean: bool = True,
625    ) -> str | Element | list[Element] | None:
626        """Return the content between text:change-start and text:change-end.
627
628        If no content exists (deletion tag), returns None (or '' if text flag
629        is True).
630        If as_text is True: returns the text content.
631        If clean is True: suppress unwanted tags (deletions marks, ...)
632        If no_header is True: existing text:h are changed in text:p
633        By default: returns a list of Element, cleaned and with headers
634
635        Arguments:
636
637            as_text -- boolean
638
639            clean -- boolean
640
641            no_header -- boolean
642
643        Return: list or Element or text
644        """
645
646        # idx = self.get_id()
647        start = self.get_start()
648        end = self.get_end()
649        if end is None or start is None:
650            if as_text:
651                return ""
652            return None
653        body = self.document_body
654        if not body:
655            body = self.root
656        return body.get_between(
657            start, end, as_text=as_text, no_header=no_header, clean=clean
658        )

Return the content between text:change-start and text:change-end.

If no content exists (deletion tag), returns None (or '' if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextChange
get_id
set_id
get_changed_region
get_change_info
get_change_element
class TextChangeStart(odfdo.TextChangeEnd):
661class TextChangeStart(TextChangeEnd):
662    """The TextChangeStart "text:change-start" element marks the start of a
663    region with content where text has been inserted or the format has
664    been changed.
665    """
666
667    _tag = "text:change-start"
668
669    def get_start(self) -> TextChangeStart:
670        """Return self."""
671        return self
672
673    def get_end(self) -> TextChangeEnd:
674        """Return the corresponding change-end tag or None."""
675        idx = self.get_id()
676        parent = self.parent
677        if parent is None:
678            raise ValueError("Can not find end tag: no parent available.")
679        body = self.document_body
680        if not body:
681            body = self.root
682        return body.get_text_change_end(idx=idx)  # type: ignore
683
684    def delete(
685        self,
686        child: Element | None = None,
687        keep_tail: bool = True,
688    ) -> None:
689        """Delete the given element from the XML tree. If no element is given,
690        "self" is deleted. The XML library may allow to continue to use an
691        element now "orphan" as long as you have a reference to it.
692
693        For TextChangeStart : delete also the end tag if exists.
694
695        Arguments:
696
697            child -- Element
698
699            keep_tail -- boolean (default to True), True for most usages.
700        """
701        if child is not None:  # act like normal delete
702            return super().delete(child, keep_tail)
703        idx = self.get_id()
704        parent = self.parent
705        if parent is None:
706            raise ValueError("cannot delete the root element")
707        body = self.document_body
708        if not body:
709            body = parent
710        end = body.get_text_change_end(idx=idx)
711        if end:
712            end.delete()
713        # act like normal delete
714        super().delete()

The TextChangeStart "text:change-start" element marks the start of a region with content where text has been inserted or the format has been changed.

def get_start(self) -> TextChangeStart:
669    def get_start(self) -> TextChangeStart:
670        """Return self."""
671        return self

Return self.

def get_end(self) -> TextChangeEnd:
673    def get_end(self) -> TextChangeEnd:
674        """Return the corresponding change-end tag or None."""
675        idx = self.get_id()
676        parent = self.parent
677        if parent is None:
678            raise ValueError("Can not find end tag: no parent available.")
679        body = self.document_body
680        if not body:
681            body = self.root
682        return body.get_text_change_end(idx=idx)  # type: ignore

Return the corresponding change-end tag or None.

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
684    def delete(
685        self,
686        child: Element | None = None,
687        keep_tail: bool = True,
688    ) -> None:
689        """Delete the given element from the XML tree. If no element is given,
690        "self" is deleted. The XML library may allow to continue to use an
691        element now "orphan" as long as you have a reference to it.
692
693        For TextChangeStart : delete also the end tag if exists.
694
695        Arguments:
696
697            child -- Element
698
699            keep_tail -- boolean (default to True), True for most usages.
700        """
701        if child is not None:  # act like normal delete
702            return super().delete(child, keep_tail)
703        idx = self.get_id()
704        parent = self.parent
705        if parent is None:
706            raise ValueError("cannot delete the root element")
707        body = self.document_body
708        if not body:
709            body = parent
710        end = body.get_text_change_end(idx=idx)
711        if end:
712            end.delete()
713        # act like normal delete
714        super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For TextChangeStart : delete also the end tag if exists.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextChangeEnd
get_deleted
get_inserted
TextChange
get_id
set_id
get_changed_region
get_change_info
get_change_element
class TextChangedRegion(odfdo.Element):
358class TextChangedRegion(Element):
359    """Each TextChangedRegion "text:changed-region" element contains a single
360    element, one of TextInsertion, TextDeletion or TextFormatChange that
361    corresponds to a change being tracked within the scope of the
362    "text:tracked-changes" element that contains the "text:changed-region"
363    instance.
364    The xml:id attribute of the TextChangedRegion is referenced
365    from the "text:change", "text:change-start" and "text:change-end"
366    elements that identify where the change applies to markup in the scope of
367    the "text:tracked-changes" element.
368
369    Warning : for this implementation, text:change should be referenced only
370              once in the scope, which is different from ODF 1.2 requirement:
371             " A "text:changed-region" can be referenced by more than one
372             change, but the corresponding referencing change mark elements
373             shall be of the same change type - insertion, format change or
374             deletion. "
375    """
376
377    _tag = "text:changed-region"
378
379    def get_change_info(self) -> Element | None:
380        """Shortcut to get the ChangeInfo element of the change
381        element child.
382
383        Return: ChangeInfo element.
384        """
385        return self.get_element("descendant::office:change-info")
386
387    def set_change_info(
388        self,
389        change_info: Element | None = None,
390        creator: str | None = None,
391        date: datetime | None = None,
392        comments: Element | list[Element] | None = None,
393    ) -> None:
394        """Shortcut to set the ChangeInfo element of the sub change element.
395        See TextInsertion.set_change_info() for details.
396
397        Arguments:
398
399             change_info -- ChangeInfo element (or None)
400
401             cretor -- str (or None)
402
403             date -- datetime (or None)
404
405             comments -- Paragraph or list of Paragraph elements (or None)
406        """
407        child = self.get_change_element()
408        if not child:
409            raise ValueError
410        child.set_change_info(  # type: ignore
411            change_info=change_info, creator=creator, date=date, comments=comments
412        )
413
414    def get_change_element(self) -> Element | None:
415        """Get the change element child. It can be either: TextInsertion,
416        TextDeletion, or TextFormatChange as an Element object.
417
418        Return: Element.
419        """
420        request = (
421            "descendant::text:insertion "
422            "| descendant::text:deletion"
423            "| descendant::text:format-change"
424        )
425        return self._filtered_element(request, 0)
426
427    def _get_text_id(self) -> str | None:
428        return self.get_attribute_string("text:id")
429
430    def _set_text_id(self, text_id: str) -> None:
431        self.set_attribute("text:id", text_id)
432
433    def _get_xml_id(self) -> str | None:
434        return self.get_attribute_string("xml:id")
435
436    def _set_xml_id(self, xml_id: str) -> None:
437        self.set_attribute("xml:id", xml_id)
438
439    def get_id(self) -> str | None:
440        """Get the "text:id" attribute.
441
442        Return: str
443        """
444        return self._get_text_id()
445
446    def set_id(self, idx: str) -> None:
447        """Set both the "text:id" and "xml:id" attributes with same value."""
448        self._set_text_id(idx)
449        self._set_xml_id(idx)

Each TextChangedRegion "text:changed-region" element contains a single element, one of TextInsertion, TextDeletion or TextFormatChange that corresponds to a change being tracked within the scope of the "text:tracked-changes" element that contains the "text:changed-region" instance. The xml:id attribute of the TextChangedRegion is referenced from the "text:change", "text:change-start" and "text:change-end" elements that identify where the change applies to markup in the scope of the "text:tracked-changes" element.

Warning : for this implementation, text:change should be referenced only once in the scope, which is different from ODF 1.2 requirement: " A "text:changed-region" can be referenced by more than one change, but the corresponding referencing change mark elements shall be of the same change type - insertion, format change or deletion. "

def get_change_info(self) -> Element | None:
379    def get_change_info(self) -> Element | None:
380        """Shortcut to get the ChangeInfo element of the change
381        element child.
382
383        Return: ChangeInfo element.
384        """
385        return self.get_element("descendant::office:change-info")

Shortcut to get the ChangeInfo element of the change element child.

Return: ChangeInfo element.

def set_change_info( self, change_info: Element | None = None, creator: str | None = None, date: datetime.datetime | None = None, comments: Element | list[Element] | None = None) -> None:
387    def set_change_info(
388        self,
389        change_info: Element | None = None,
390        creator: str | None = None,
391        date: datetime | None = None,
392        comments: Element | list[Element] | None = None,
393    ) -> None:
394        """Shortcut to set the ChangeInfo element of the sub change element.
395        See TextInsertion.set_change_info() for details.
396
397        Arguments:
398
399             change_info -- ChangeInfo element (or None)
400
401             cretor -- str (or None)
402
403             date -- datetime (or None)
404
405             comments -- Paragraph or list of Paragraph elements (or None)
406        """
407        child = self.get_change_element()
408        if not child:
409            raise ValueError
410        child.set_change_info(  # type: ignore
411            change_info=change_info, creator=creator, date=date, comments=comments
412        )

Shortcut to set the ChangeInfo element of the sub change element. See TextInsertion.set_change_info() for details.

Arguments:

 change_info -- ChangeInfo element (or None)

 cretor -- str (or None)

 date -- datetime (or None)

 comments -- Paragraph or list of Paragraph elements (or None)
def get_change_element(self) -> Element | None:
414    def get_change_element(self) -> Element | None:
415        """Get the change element child. It can be either: TextInsertion,
416        TextDeletion, or TextFormatChange as an Element object.
417
418        Return: Element.
419        """
420        request = (
421            "descendant::text:insertion "
422            "| descendant::text:deletion"
423            "| descendant::text:format-change"
424        )
425        return self._filtered_element(request, 0)

Get the change element child. It can be either: TextInsertion, TextDeletion, or TextFormatChange as an Element object.

Return: Element.

def get_id(self) -> str | None:
439    def get_id(self) -> str | None:
440        """Get the "text:id" attribute.
441
442        Return: str
443        """
444        return self._get_text_id()

Get the "text:id" attribute.

Return: str

def set_id(self, idx: str) -> None:
446    def set_id(self, idx: str) -> None:
447        """Set both the "text:id" and "xml:id" attributes with same value."""
448        self._set_text_id(idx)
449        self._set_xml_id(idx)

Set both the "text:id" and "xml:id" attributes with same value.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TextDeletion(odfdo.TextInsertion):
251class TextDeletion(TextInsertion):
252    """The TextDeletion "text:deletion" contains information that identifies
253    the person responsible for a deletion and the date of that deletion.
254    This information may also contain one or more Paragraph which contains
255    a comment on the deletion. The TextDeletion element may also contain
256    content that was deleted while change tracking was enabled. The position
257    where the text was deleted is marked by a "text:change" element. Deleted
258    text is contained in a paragraph element. To reconstruct the original
259    text, the paragraph containing the deleted text is merged with its
260    surrounding paragraph or heading element. To reconstruct the text before
261    a deletion took place:
262      - If the change mark is inside a paragraph, insert the content that was
263      deleted, but remove all leading start tags up to and including the
264      first "text:p" element and all trailing end tags up to and including
265      the last "/text:p" or "/text:h" element. If the last trailing element
266      is a "/text:h", change the end tag "/text:p" following this insertion
267      to a "/text:h" element.
268      - If the change mark is inside a heading, insert the content that was
269      deleted, but remove all leading start tags up to and including the
270      first "text:h" element and all trailing end tags up to and including
271      the last "/text:h" or "/text:p" element. If the last trailing element
272      is a "/text:p", change the end tag "/text:h" following this insertion
273      to a "/text:p" element.
274      - Otherwise, copy the text content of the "text:deletion" element in
275      place of the change mark.
276    """
277
278    _tag = "text:deletion"
279
280    def get_deleted(
281        self,
282        as_text: bool = False,
283        no_header: bool = False,
284    ) -> str | list[Element] | None:
285        """Get the deleted informations stored in the TextDeletion.
286        If as_text is True: returns the text content.
287        If no_header is True: existing Heading are changed in Paragraph
288
289        Arguments:
290
291            as_text -- boolean
292
293            no_header -- boolean
294
295        Return: Paragraph and Header list
296        """
297        children = self.children
298        inner = [elem for elem in children if elem.tag != "office:change-info"]
299        if no_header:  # crude replace t:h by t:p
300            new_inner = []
301            for element in inner:
302                if element.tag == "text:h":
303                    children = element.children
304                    text = element.text
305                    para = Element.from_tag("text:p")
306                    para.text = text
307                    for child in children:
308                        para.append(child)
309                    new_inner.append(para)
310                else:
311                    new_inner.append(element)
312            inner = new_inner
313        if as_text:
314            return "\n".join([elem.get_formatted_text(context=None) for elem in inner])
315        return inner
316
317    def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None:
318        """Set the deleted informations stored in the TextDeletion. An
319        actual content that was deleted is expected, embeded in a Paragraph
320        element or Header.
321
322        Arguments:
323
324            paragraph_or_list -- Paragraph or Header element (or list)
325        """
326        for element in self.get_deleted():  # type: ignore
327            self.delete(element)  # type: ignore
328        if isinstance(paragraph_or_list, Element):
329            paragraph_or_list = [paragraph_or_list]
330        for element in paragraph_or_list:
331            self.append(element)
332
333    def get_inserted(
334        self,
335        as_text: bool = False,
336        no_header: bool = False,
337        clean: bool = True,
338    ) -> str | Element | list[Element] | None:
339        """Return None."""
340        if as_text:
341            return ""
342        return None

The TextDeletion "text:deletion" contains information that identifies the person responsible for a deletion and the date of that deletion. This information may also contain one or more Paragraph which contains a comment on the deletion. The TextDeletion element may also contain content that was deleted while change tracking was enabled. The position where the text was deleted is marked by a "text:change" element. Deleted text is contained in a paragraph element. To reconstruct the original text, the paragraph containing the deleted text is merged with its surrounding paragraph or heading element. To reconstruct the text before a deletion took place:

  • If the change mark is inside a paragraph, insert the content that was deleted, but remove all leading start tags up to and including the first "text:p" element and all trailing end tags up to and including the last "/text:p" or "/text:h" element. If the last trailing element is a "/text:h", change the end tag "/text:p" following this insertion to a "/text:h" element.
  • If the change mark is inside a heading, insert the content that was deleted, but remove all leading start tags up to and including the first "text:h" element and all trailing end tags up to and including the last "/text:h" or "/text:p" element. If the last trailing element is a "/text:p", change the end tag "/text:h" following this insertion to a "/text:p" element.
  • Otherwise, copy the text content of the "text:deletion" element in place of the change mark.
def get_deleted( self, as_text: bool = False, no_header: bool = False) -> str | list[Element] | None:
280    def get_deleted(
281        self,
282        as_text: bool = False,
283        no_header: bool = False,
284    ) -> str | list[Element] | None:
285        """Get the deleted informations stored in the TextDeletion.
286        If as_text is True: returns the text content.
287        If no_header is True: existing Heading are changed in Paragraph
288
289        Arguments:
290
291            as_text -- boolean
292
293            no_header -- boolean
294
295        Return: Paragraph and Header list
296        """
297        children = self.children
298        inner = [elem for elem in children if elem.tag != "office:change-info"]
299        if no_header:  # crude replace t:h by t:p
300            new_inner = []
301            for element in inner:
302                if element.tag == "text:h":
303                    children = element.children
304                    text = element.text
305                    para = Element.from_tag("text:p")
306                    para.text = text
307                    for child in children:
308                        para.append(child)
309                    new_inner.append(para)
310                else:
311                    new_inner.append(element)
312            inner = new_inner
313        if as_text:
314            return "\n".join([elem.get_formatted_text(context=None) for elem in inner])
315        return inner

Get the deleted informations stored in the TextDeletion. If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph

Arguments:

as_text -- boolean

no_header -- boolean

Return: Paragraph and Header list

def set_deleted( self, paragraph_or_list: Element | list[Element]) -> None:
317    def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None:
318        """Set the deleted informations stored in the TextDeletion. An
319        actual content that was deleted is expected, embeded in a Paragraph
320        element or Header.
321
322        Arguments:
323
324            paragraph_or_list -- Paragraph or Header element (or list)
325        """
326        for element in self.get_deleted():  # type: ignore
327            self.delete(element)  # type: ignore
328        if isinstance(paragraph_or_list, Element):
329            paragraph_or_list = [paragraph_or_list]
330        for element in paragraph_or_list:
331            self.append(element)

Set the deleted informations stored in the TextDeletion. An actual content that was deleted is expected, embeded in a Paragraph element or Header.

Arguments:

paragraph_or_list -- Paragraph or Header element (or list)
def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
333    def get_inserted(
334        self,
335        as_text: bool = False,
336        no_header: bool = False,
337        clean: bool = True,
338    ) -> str | Element | list[Element] | None:
339        """Return None."""
340        if as_text:
341            return ""
342        return None

Return None.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextInsertion
get_change_info
set_change_info
class TextFormatChange(odfdo.TextInsertion):
345class TextFormatChange(TextInsertion):
346    """The TextFormatChange "text:format-change" element represents any change
347    in formatting attributes. The region where the change took place is
348    marked by "text:change-start", "text:change-end" or "text:change"
349    elements.
350
351    Note: This element does not contain formatting changes that have taken
352    place.
353    """
354
355    _tag = "text:format-change"

The TextFormatChange "text:format-change" element represents any change in formatting attributes. The region where the change took place is marked by "text:change-start", "text:change-end" or "text:change" elements.

Note: This element does not contain formatting changes that have taken place.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextInsertion
get_deleted
get_inserted
get_change_info
set_change_info
class TextInsertion(odfdo.Element):
134class TextInsertion(Element):
135    """The TextInsertion "text:insertion" element contains the information
136    that identifies the person responsible for a change and the date of
137    that change. This information may also contain one or more "text:p"
138    Paragraph which contain a comment on the insertion. The
139    TextInsertion element's parent "text:changed-region" element has an
140    xml:id or text:id attribute, the value of which binds that parent
141    element to the text:change-id attribute on the "text:change-start"
142    and "text:change-end" elements.
143    """
144
145    _tag = "text:insertion"
146
147    def get_deleted(
148        self,
149        as_text: bool = False,
150        no_header: bool = False,
151    ) -> str | list[Element] | None:
152        """Return: None."""
153        if as_text:
154            return ""
155        return None
156
157    def get_inserted(
158        self,
159        as_text: bool = False,
160        no_header: bool = False,
161        clean: bool = True,
162    ) -> str | Element | list[Element] | None:
163        """Shortcut to text:change-start.get_inserted(). Return the content
164        between text:change-start and text:change-end.
165
166        If as_text is True: returns the text content.
167        If no_header is True: existing Heading are changed in Paragraph
168        If no_header is True: existing text:h are changed in text:p
169        By default: returns a list of Element, cleaned and with headers
170
171        Arguments:
172
173            as_text -- boolean
174
175            clean -- boolean
176
177            no_header -- boolean
178
179        Return: list or Element or text
180        """
181        current = self.parent  # text:changed-region
182        if not current:
183            raise ValueError
184        idx = current.get_id()  # type: ignore
185        body = self.document_body
186        if not body:
187            body = self.root
188        text_change = body.get_text_change_start(idx=idx)
189        if not text_change:
190            raise ValueError
191        return text_change.get_inserted(  # type: ignore
192            as_text=as_text, no_header=no_header, clean=clean
193        )
194
195    def get_change_info(self) -> Element | None:
196        """Get the ChangeInfo child of the element.
197
198        Return: ChangeInfo element.
199        """
200        return self.get_element("descendant::office:change-info")
201
202    def set_change_info(
203        self,
204        change_info: Element | None = None,
205        creator: str | None = None,
206        date: datetime | None = None,
207        comments: Element | list[Element] | None = None,
208    ) -> None:
209        """Set the ChangeInfo element for the change element. If change_info
210        is not provided, creator, date and comments will be used to build a
211        suitable change info element. Default for creator is 'Unknown',
212        default for date is current time and default for comments is no
213        comment at all.
214        The new change info element will replace any existant ChangeInfo.
215
216        Arguments:
217
218             change_info -- ChangeInfo element (or None)
219
220             cretor -- str (or None)
221
222             date -- datetime (or None)
223
224             comments -- Paragraph or list of Paragraph elements (or None)
225        """
226        if change_info is None:
227            new_change_info = ChangeInfo(creator, date)
228            if comments is not None:
229                if isinstance(comments, Element):
230                    # single pararagraph comment
231                    comments_list = [comments]
232                else:
233                    comments_list = comments
234                # assume iterable of Paragraph
235                for paragraph in comments_list:
236                    if not isinstance(paragraph, Paragraph):
237                        raise TypeError(f"Not a Paragraph: '{paragraph!r}'")
238                    new_change_info.insert(paragraph, xmlposition=LAST_CHILD)
239        else:
240            if not isinstance(change_info, ChangeInfo):
241                raise TypeError(f"Not a ChangeInfo: '{change_info!r}'")
242            new_change_info = change_info
243
244        old = self.get_change_info()
245        if old is not None:
246            self.replace_element(old, new_change_info)
247        else:
248            self.insert(new_change_info, xmlposition=FIRST_CHILD)

The TextInsertion "text:insertion" element contains the information that identifies the person responsible for a change and the date of that change. This information may also contain one or more "text:p" Paragraph which contain a comment on the insertion. The TextInsertion element's parent "text:changed-region" element has an xml:id or text:id attribute, the value of which binds that parent element to the text:change-id attribute on the "text:change-start" and "text:change-end" elements.

def get_deleted( self, as_text: bool = False, no_header: bool = False) -> str | list[Element] | None:
147    def get_deleted(
148        self,
149        as_text: bool = False,
150        no_header: bool = False,
151    ) -> str | list[Element] | None:
152        """Return: None."""
153        if as_text:
154            return ""
155        return None

Return: None.

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
157    def get_inserted(
158        self,
159        as_text: bool = False,
160        no_header: bool = False,
161        clean: bool = True,
162    ) -> str | Element | list[Element] | None:
163        """Shortcut to text:change-start.get_inserted(). Return the content
164        between text:change-start and text:change-end.
165
166        If as_text is True: returns the text content.
167        If no_header is True: existing Heading are changed in Paragraph
168        If no_header is True: existing text:h are changed in text:p
169        By default: returns a list of Element, cleaned and with headers
170
171        Arguments:
172
173            as_text -- boolean
174
175            clean -- boolean
176
177            no_header -- boolean
178
179        Return: list or Element or text
180        """
181        current = self.parent  # text:changed-region
182        if not current:
183            raise ValueError
184        idx = current.get_id()  # type: ignore
185        body = self.document_body
186        if not body:
187            body = self.root
188        text_change = body.get_text_change_start(idx=idx)
189        if not text_change:
190            raise ValueError
191        return text_change.get_inserted(  # type: ignore
192            as_text=as_text, no_header=no_header, clean=clean
193        )

Shortcut to text:change-start.get_inserted(). Return the content between text:change-start and text:change-end.

If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text

def get_change_info(self) -> Element | None:
195    def get_change_info(self) -> Element | None:
196        """Get the ChangeInfo child of the element.
197
198        Return: ChangeInfo element.
199        """
200        return self.get_element("descendant::office:change-info")

Get the ChangeInfo child of the element.

Return: ChangeInfo element.

def set_change_info( self, change_info: Element | None = None, creator: str | None = None, date: datetime.datetime | None = None, comments: Element | list[Element] | None = None) -> None:
202    def set_change_info(
203        self,
204        change_info: Element | None = None,
205        creator: str | None = None,
206        date: datetime | None = None,
207        comments: Element | list[Element] | None = None,
208    ) -> None:
209        """Set the ChangeInfo element for the change element. If change_info
210        is not provided, creator, date and comments will be used to build a
211        suitable change info element. Default for creator is 'Unknown',
212        default for date is current time and default for comments is no
213        comment at all.
214        The new change info element will replace any existant ChangeInfo.
215
216        Arguments:
217
218             change_info -- ChangeInfo element (or None)
219
220             cretor -- str (or None)
221
222             date -- datetime (or None)
223
224             comments -- Paragraph or list of Paragraph elements (or None)
225        """
226        if change_info is None:
227            new_change_info = ChangeInfo(creator, date)
228            if comments is not None:
229                if isinstance(comments, Element):
230                    # single pararagraph comment
231                    comments_list = [comments]
232                else:
233                    comments_list = comments
234                # assume iterable of Paragraph
235                for paragraph in comments_list:
236                    if not isinstance(paragraph, Paragraph):
237                        raise TypeError(f"Not a Paragraph: '{paragraph!r}'")
238                    new_change_info.insert(paragraph, xmlposition=LAST_CHILD)
239        else:
240            if not isinstance(change_info, ChangeInfo):
241                raise TypeError(f"Not a ChangeInfo: '{change_info!r}'")
242            new_change_info = change_info
243
244        old = self.get_change_info()
245        if old is not None:
246            self.replace_element(old, new_change_info)
247        else:
248            self.insert(new_change_info, xmlposition=FIRST_CHILD)

Set the ChangeInfo element for the change element. If change_info is not provided, creator, date and comments will be used to build a suitable change info element. Default for creator is 'Unknown', default for date is current time and default for comments is no comment at all. The new change info element will replace any existant ChangeInfo.

Arguments:

 change_info -- ChangeInfo element (or None)

 cretor -- str (or None)

 date -- datetime (or None)

 comments -- Paragraph or list of Paragraph elements (or None)
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TocEntryTemplate(odfdo.Element):
467class TocEntryTemplate(Element):
468    """ODF "text:table-of-content-entry-template"
469
470    Arguments:
471
472        style -- str
473    """
474
475    _tag = "text:table-of-content-entry-template"
476    _properties = (PropDef("style", "text:style-name"),)
477
478    def __init__(
479        self,
480        style: str | None = None,
481        outline_level: int | None = None,
482        **kwargs: Any,
483    ) -> None:
484        super().__init__(**kwargs)
485        if self._do_init:
486            if style:
487                self.style = style
488            if outline_level:
489                self.outline_level = outline_level
490
491    @property
492    def outline_level(self) -> int | None:
493        return self.get_attribute_integer("text:outline-level")
494
495    @outline_level.setter
496    def outline_level(self, level: int) -> None:
497        self.set_attribute("text:outline-level", str(level))
498
499    def complete_defaults(self) -> None:
500        self.append(Element.from_tag("text:index-entry-chapter"))
501        self.append(Element.from_tag("text:index-entry-text"))
502        self.append(Element.from_tag("text:index-entry-text"))
503        ts = Element.from_tag("text:index-entry-text")
504        ts.set_style_attribute("style:type", "right")
505        ts.set_style_attribute("style:leader-char", ".")
506        self.append(ts)
507        self.append(Element.from_tag("text:index-entry-page-number"))

ODF "text:table-of-content-entry-template"

Arguments:

style -- str
TocEntryTemplate( style: str | None = None, outline_level: int | None = None, **kwargs: Any)
478    def __init__(
479        self,
480        style: str | None = None,
481        outline_level: int | None = None,
482        **kwargs: Any,
483    ) -> None:
484        super().__init__(**kwargs)
485        if self._do_init:
486            if style:
487                self.style = style
488            if outline_level:
489                self.outline_level = outline_level
outline_level: int | None
491    @property
492    def outline_level(self) -> int | None:
493        return self.get_attribute_integer("text:outline-level")
def complete_defaults(self) -> None:
499    def complete_defaults(self) -> None:
500        self.append(Element.from_tag("text:index-entry-chapter"))
501        self.append(Element.from_tag("text:index-entry-text"))
502        self.append(Element.from_tag("text:index-entry-text"))
503        ts = Element.from_tag("text:index-entry-text")
504        ts.set_style_attribute("style:type", "right")
505        ts.set_style_attribute("style:leader-char", ".")
506        self.append(ts)
507        self.append(Element.from_tag("text:index-entry-page-number"))
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TrackedChanges(odfdo.Element):
452class TrackedChanges(Element):
453    """The TrackedChanges "text:tracked-changes" element acts as a container
454    for TextChangedRegion elements that represent changes in a certain
455    scope of an OpenDocument document. This scope is the element in which
456    the TrackedChanges element occurs. Changes in this scope shall be
457    tracked by TextChangedRegion elements contained in the
458    TrackedChanges element in this scope. If a TrackedChanges
459    element is absent, there are no tracked changes in the corresponding
460    scope. In this case, all change mark elements in this scope shall be
461    ignored.
462    """
463
464    _tag = "text:tracked-changes"
465
466    def get_changed_regions(
467        self,
468        creator: str | None = None,
469        date: datetime | None = None,
470        content: str | None = None,
471        role: str | None = None,
472    ) -> list[Element]:
473        changed_regions = self._filtered_elements(
474            "text:changed-region",
475            dc_creator=creator,
476            dc_date=date,
477            content=content,
478        )
479        if role is None:
480            return changed_regions
481        result: list[Element] = []
482        for regien in changed_regions:
483            changed = regien.get_change_element()  # type: ignore
484            if not changed:
485                continue
486            if changed.tag.endswith(role):
487                result.append(regien)
488        return result
489
490    def get_changed_region(
491        self,
492        position: int = 0,
493        text_id: str | None = None,
494        creator: str | None = None,
495        date: datetime | None = None,
496        content: str | None = None,
497    ) -> Element | None:
498        return self._filtered_element(
499            "text:changed-region",
500            position,
501            text_id=text_id,
502            dc_creator=creator,
503            dc_date=date,
504            content=content,
505        )

The TrackedChanges "text:tracked-changes" element acts as a container for TextChangedRegion elements that represent changes in a certain scope of an OpenDocument document. This scope is the element in which the TrackedChanges element occurs. Changes in this scope shall be tracked by TextChangedRegion elements contained in the TrackedChanges element in this scope. If a TrackedChanges element is absent, there are no tracked changes in the corresponding scope. In this case, all change mark elements in this scope shall be ignored.

def get_changed_regions( self, creator: str | None = None, date: datetime.datetime | None = None, content: str | None = None, role: str | None = None) -> list[Element]:
466    def get_changed_regions(
467        self,
468        creator: str | None = None,
469        date: datetime | None = None,
470        content: str | None = None,
471        role: str | None = None,
472    ) -> list[Element]:
473        changed_regions = self._filtered_elements(
474            "text:changed-region",
475            dc_creator=creator,
476            dc_date=date,
477            content=content,
478        )
479        if role is None:
480            return changed_regions
481        result: list[Element] = []
482        for regien in changed_regions:
483            changed = regien.get_change_element()  # type: ignore
484            if not changed:
485                continue
486            if changed.tag.endswith(role):
487                result.append(regien)
488        return result
def get_changed_region( self, position: int = 0, text_id: str | None = None, creator: str | None = None, date: datetime.datetime | None = None, content: str | None = None) -> Element | None:
490    def get_changed_region(
491        self,
492        position: int = 0,
493        text_id: str | None = None,
494        creator: str | None = None,
495        date: datetime | None = None,
496        content: str | None = None,
497    ) -> Element | None:
498        return self._filtered_element(
499            "text:changed-region",
500            position,
501            text_id=text_id,
502            dc_creator=creator,
503            dc_date=date,
504            content=content,
505        )
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserDefined(odfdo.ElementTyped):
212class UserDefined(ElementTyped):
213    """Return a user defined field "text:user-defined". If the current
214    document is provided, try to extract the content of the meta user defined
215    field of same name.
216
217    Arguments:
218
219        name -- str, name of the user defined field
220
221        value -- python typed value, value of the field
222
223        value_type -- str, office:value-type known type
224
225        text -- str
226
227        style -- str
228
229        from_document -- ODF document
230    """
231
232    _tag = "text:user-defined"
233    _properties = (
234        PropDef("name", "text:name"),
235        PropDef("style", "style:data-style-name"),
236    )
237
238    def __init__(
239        self,
240        name: str = "",
241        value: Any = None,
242        value_type: str | None = None,
243        text: str | None = None,
244        style: str | None = None,
245        from_document: Document | None = None,
246        **kwargs: Any,
247    ) -> None:
248        super().__init__(**kwargs)
249        if self._do_init:
250            if name:
251                self.name = name
252            if style:
253                self.style = style
254            if from_document is not None:
255                meta_infos = from_document.meta
256                content = meta_infos.get_user_defined_metadata_of_name(name)
257                if content is not None:
258                    value = content.get("value", None)
259                    value_type = content.get("value_type", None)
260                    text = content.get("text", None)
261            text = self.set_value_and_type(
262                value=value, value_type=value_type, text=text
263            )
264            self.text = text  # type: ignore

Return a user defined field "text:user-defined". If the current document is provided, try to extract the content of the meta user defined field of same name.

Arguments:

name -- str, name of the user defined field

value -- python typed value, value of the field

value_type -- str, office:value-type known type

text -- str

style -- str

from_document -- ODF document
UserDefined( name: str = '', value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, from_document: Document | None = None, **kwargs: Any)
238    def __init__(
239        self,
240        name: str = "",
241        value: Any = None,
242        value_type: str | None = None,
243        text: str | None = None,
244        style: str | None = None,
245        from_document: Document | None = None,
246        **kwargs: Any,
247    ) -> None:
248        super().__init__(**kwargs)
249        if self._do_init:
250            if name:
251                self.name = name
252            if style:
253                self.style = style
254            if from_document is not None:
255                meta_infos = from_document.meta
256                content = meta_infos.get_user_defined_metadata_of_name(name)
257                if content is not None:
258                    value = content.get("value", None)
259                    value_type = content.get("value_type", None)
260                    text = content.get("text", None)
261            text = self.set_value_and_type(
262                value=value, value_type=value_type, text=text
263            )
264            self.text = text  # type: ignore
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldDecl(odfdo.ElementTyped):
146class UserFieldDecl(ElementTyped):
147    _tag = "text:user-field-decl"
148    _properties = (PropDef("name", "text:name"),)
149
150    def __init__(
151        self,
152        name: str | None = None,
153        value: Any = None,
154        value_type: str | None = None,
155        **kwargs: Any,
156    ) -> None:
157        super().__init__(**kwargs)
158        if self._do_init:
159            if name:
160                self.name = name
161            self.set_value_and_type(value=value, value_type=value_type)
162
163    def set_value(self, value: Any) -> None:
164        name = self.get_attribute("text:name")
165        self.clear()
166        self.set_value_and_type(value=value)
167        self.set_attribute("text:name", name)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

UserFieldDecl( name: str | None = None, value: Any = None, value_type: str | None = None, **kwargs: Any)
150    def __init__(
151        self,
152        name: str | None = None,
153        value: Any = None,
154        value_type: str | None = None,
155        **kwargs: Any,
156    ) -> None:
157        super().__init__(**kwargs)
158        if self._do_init:
159            if name:
160                self.name = name
161            self.set_value_and_type(value=value, value_type=value_type)
def set_value(self, value: Any) -> None:
163    def set_value(self, value: Any) -> None:
164        name = self.get_attribute("text:name")
165        self.clear()
166        self.set_value_and_type(value=value)
167        self.set_attribute("text:name", name)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldDecls(odfdo.Element):
142class UserFieldDecls(Element):
143    _tag = "text:user-field-decls"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldGet(odfdo.ElementTyped):
173class UserFieldGet(ElementTyped):
174    _tag = "text:user-field-get"
175    _properties = (
176        PropDef("name", "text:name"),
177        PropDef("style", "style:data-style-name"),
178    )
179
180    def __init__(
181        self,
182        name: str | None = None,
183        value: Any = None,
184        value_type: str | None = None,
185        text: str | None = None,
186        style: str | None = None,
187        **kwargs: Any,
188    ) -> None:
189        super().__init__(**kwargs)
190        if self._do_init:
191            if name:
192                self.name = name
193            text = self.set_value_and_type(
194                value=value, value_type=value_type, text=text
195            )
196            self.text = text  # type: ignore
197
198            if style:
199                self.style = style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

UserFieldGet( name: str | None = None, value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, **kwargs: Any)
180    def __init__(
181        self,
182        name: str | None = None,
183        value: Any = None,
184        value_type: str | None = None,
185        text: str | None = None,
186        style: str | None = None,
187        **kwargs: Any,
188    ) -> None:
189        super().__init__(**kwargs)
190        if self._do_init:
191            if name:
192                self.name = name
193            text = self.set_value_and_type(
194                value=value, value_type=value_type, text=text
195            )
196            self.text = text  # type: ignore
197
198            if style:
199                self.style = style
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldInput(odfdo.UserFieldGet):
205class UserFieldInput(UserFieldGet):
206    _tag = "text:user-field-input"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
UserFieldGet
UserFieldGet
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarChapter(odfdo.Element):
376class VarChapter(Element):
377    _tag = "text:chapter"
378    _properties = (
379        PropDef("display", "text:display"),
380        PropDef("outline_level", "text:outline-level"),
381    )
382    DISPLAY_VALUE_CHOICE = {  # noqa: RUF012
383        "number",
384        "name",
385        "number-and-name",
386        "plain-number",
387        "plain-number-and-name",
388    }
389
390    def __init__(
391        self,
392        display: str | None = "name",
393        outline_level: str | None = None,
394        **kwargs: Any,
395    ) -> None:
396        """display can be: 'number', 'name', 'number-and-name', 'plain-number' or
397        'plain-number-and-name'
398        """
399        super().__init__(**kwargs)
400        if self._do_init:
401            if display not in VarChapter.DISPLAY_VALUE_CHOICE:
402                raise ValueError("unknown display value: %s" % display)
403            self.display = display
404            if outline_level is not None:
405                self.outline_level = outline_level

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarChapter( display: str | None = 'name', outline_level: str | None = None, **kwargs: Any)
390    def __init__(
391        self,
392        display: str | None = "name",
393        outline_level: str | None = None,
394        **kwargs: Any,
395    ) -> None:
396        """display can be: 'number', 'name', 'number-and-name', 'plain-number' or
397        'plain-number-and-name'
398        """
399        super().__init__(**kwargs)
400        if self._do_init:
401            if display not in VarChapter.DISPLAY_VALUE_CHOICE:
402                raise ValueError("unknown display value: %s" % display)
403            self.display = display
404            if outline_level is not None:
405                self.outline_level = outline_level

display can be: 'number', 'name', 'number-and-name', 'plain-number' or 'plain-number-and-name'

DISPLAY_VALUE_CHOICE = {'plain-number-and-name', 'number', 'number-and-name', 'plain-number', 'name'}
display: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
outline_level: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarCreationDate(odfdo.Element):
456class VarCreationDate(Element):
457    _tag = "text:creation-date"
458    _properties = (
459        PropDef("fixed", "text:fixed"),
460        PropDef("data_style", "style:data-style-name"),
461    )
462
463    def __init__(
464        self,
465        fixed: bool = False,
466        data_style: str | None = None,
467        **kwargs: Any,
468    ) -> None:
469        super().__init__(**kwargs)
470        if self._do_init:
471            if fixed:
472                self.fixed = True
473            if data_style:
474                self.data_style = data_style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarCreationDate(fixed: bool = False, data_style: str | None = None, **kwargs: Any)
463    def __init__(
464        self,
465        fixed: bool = False,
466        data_style: str | None = None,
467        **kwargs: Any,
468    ) -> None:
469        super().__init__(**kwargs)
470        if self._do_init:
471            if fixed:
472                self.fixed = True
473            if data_style:
474                self.data_style = data_style
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarCreationTime(odfdo.Element):
480class VarCreationTime(Element):
481    _tag = "text:creation-time"
482    _properties = (
483        PropDef("fixed", "text:fixed"),
484        PropDef("data_style", "style:data-style-name"),
485    )
486
487    def __init__(
488        self,
489        fixed: bool = False,
490        data_style: str | None = None,
491        **kwargs: Any,
492    ) -> None:
493        super().__init__(**kwargs)
494        if self._do_init:
495            if fixed:
496                self.fixed = True
497            if data_style:
498                self.data_style = data_style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarCreationTime(fixed: bool = False, data_style: str | None = None, **kwargs: Any)
487    def __init__(
488        self,
489        fixed: bool = False,
490        data_style: str | None = None,
491        **kwargs: Any,
492    ) -> None:
493        super().__init__(**kwargs)
494        if self._do_init:
495            if fixed:
496                self.fixed = True
497            if data_style:
498                self.data_style = data_style
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDate(odfdo.Element):
305class VarDate(Element):
306    _tag = "text:date"
307    _properties = (
308        PropDef("date", "text:date-value"),
309        PropDef("fixed", "text:fixed"),
310        PropDef("data_style", "style:data-style-name"),
311        PropDef("date_adjust", "text:date-adjust"),
312    )
313
314    def __init__(
315        self,
316        date: datetime | None = None,
317        fixed: bool = False,
318        data_style: str | None = None,
319        text: str | None = None,
320        date_adjust: timedelta | None = None,
321        **kwargs: Any,
322    ) -> None:
323        super().__init__(**kwargs)
324        if self._do_init:
325            if date:
326                self.date = DateTime.encode(date)
327            if fixed:
328                self.fixed = True
329            if data_style is not None:
330                self.data_style = data_style
331            if text is None and date is not None:
332                text = Date.encode(date)
333            self.text = text  # type: ignore
334            if date_adjust is not None:
335                self.date_adjust = Duration.encode(date_adjust)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarDate( date: datetime.datetime | None = None, fixed: bool = False, data_style: str | None = None, text: str | None = None, date_adjust: datetime.timedelta | None = None, **kwargs: Any)
314    def __init__(
315        self,
316        date: datetime | None = None,
317        fixed: bool = False,
318        data_style: str | None = None,
319        text: str | None = None,
320        date_adjust: timedelta | None = None,
321        **kwargs: Any,
322    ) -> None:
323        super().__init__(**kwargs)
324        if self._do_init:
325            if date:
326                self.date = DateTime.encode(date)
327            if fixed:
328                self.fixed = True
329            if data_style is not None:
330                self.data_style = data_style
331            if text is None and date is not None:
332                text = Date.encode(date)
333            self.text = text  # type: ignore
334            if date_adjust is not None:
335                self.date_adjust = Duration.encode(date_adjust)
date: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
date_adjust: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDecl(odfdo.Element):
40class VarDecl(Element):
41    _tag = "text:variable-decl"
42    _properties = (
43        PropDef("name", "text:name"),
44        PropDef("value_type", "office:value-type"),
45    )
46
47    def __init__(
48        self,
49        name: str | None = None,
50        value_type: str | None = None,
51        **kwargs: Any,
52    ) -> None:
53        super().__init__(**kwargs)
54        if self._do_init:
55            if name:
56                self.name = name
57            if value_type:
58                self.value_type = value_type

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarDecl( name: str | None = None, value_type: str | None = None, **kwargs: Any)
47    def __init__(
48        self,
49        name: str | None = None,
50        value_type: str | None = None,
51        **kwargs: Any,
52    ) -> None:
53        super().__init__(**kwargs)
54        if self._do_init:
55            if name:
56                self.name = name
57            if value_type:
58                self.value_type = value_type
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
value_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDecls(odfdo.Element):
36class VarDecls(Element):
37    _tag = "text:variable-decls"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDescription(odfdo.VarInitialCreator):
504class VarDescription(VarInitialCreator):
505    _tag = "text:description"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarFileName(odfdo.Element):
411class VarFileName(Element):
412    _tag = "text:file-name"
413    _properties = (
414        PropDef("display", "text:display"),
415        PropDef("fixed", "text:fixed"),
416    )
417    DISPLAY_VALUE_CHOICE = {  # noqa: RUF012
418        "full",
419        "path",
420        "name",
421        "name-and-extension",
422    }
423
424    def __init__(
425        self,
426        display: str | None = "full",
427        fixed: bool = False,
428        **kwargs: Any,
429    ) -> None:
430        """display can be: 'full', 'path', 'name' or 'name-and-extension'"""
431        super().__init__(**kwargs)
432        if self._do_init:
433            if display not in VarFileName.DISPLAY_VALUE_CHOICE:
434                raise ValueError("unknown display value: %s" % display)
435            self.display = display
436            if fixed:
437                self.fixed = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarFileName(display: str | None = 'full', fixed: bool = False, **kwargs: Any)
424    def __init__(
425        self,
426        display: str | None = "full",
427        fixed: bool = False,
428        **kwargs: Any,
429    ) -> None:
430        """display can be: 'full', 'path', 'name' or 'name-and-extension'"""
431        super().__init__(**kwargs)
432        if self._do_init:
433            if display not in VarFileName.DISPLAY_VALUE_CHOICE:
434                raise ValueError("unknown display value: %s" % display)
435            self.display = display
436            if fixed:
437                self.fixed = True

display can be: 'full', 'path', 'name' or 'name-and-extension'

DISPLAY_VALUE_CHOICE = {'name-and-extension', 'path', 'full', 'name'}
display: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarGet(odfdo.ElementTyped):
111class VarGet(ElementTyped):
112    _tag = "text:variable-get"
113    _properties = (
114        PropDef("name", "text:name"),
115        PropDef("style", "style:data-style-name"),
116    )
117
118    def __init__(
119        self,
120        name: str | None = None,
121        value: Any = None,
122        value_type: str | None = None,
123        text: str | None = None,
124        style: str | None = None,
125        **kwargs: Any,
126    ) -> None:
127        super().__init__(**kwargs)
128        if self._do_init:
129            if name:
130                self.name = name
131            if style:
132                self.style = style
133            text = self.set_value_and_type(
134                value=value, value_type=value_type, text=text
135            )
136            self.text = text  # type: ignore

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarGet( name: str | None = None, value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, **kwargs: Any)
118    def __init__(
119        self,
120        name: str | None = None,
121        value: Any = None,
122        value_type: str | None = None,
123        text: str | None = None,
124        style: str | None = None,
125        **kwargs: Any,
126    ) -> None:
127        super().__init__(**kwargs)
128        if self._do_init:
129            if name:
130                self.name = name
131            if style:
132                self.style = style
133            text = self.set_value_and_type(
134                value=value, value_type=value_type, text=text
135            )
136            self.text = text  # type: ignore
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarInitialCreator(odfdo.Element):
443class VarInitialCreator(Element):
444    _tag = "text:initial-creator"
445    _properties = (PropDef("fixed", "text:fixed"),)
446
447    def __init__(self, fixed: bool = False, **kwargs: Any) -> None:
448        super().__init__(**kwargs)
449        if self._do_init and fixed:
450            self.fixed = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarInitialCreator(fixed: bool = False, **kwargs: Any)
447    def __init__(self, fixed: bool = False, **kwargs: Any) -> None:
448        super().__init__(**kwargs)
449        if self._do_init and fixed:
450            self.fixed = True
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarKeywords(odfdo.VarInitialCreator):
525class VarKeywords(VarInitialCreator):
526    _tag = "text:keywords"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarPageCount(odfdo.Element):
301class VarPageCount(Element):
302    _tag = "text:page-count"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarPageNumber(odfdo.Element):
270class VarPageNumber(Element):
271    """
272    select_page -- string in ('previous', 'current', 'next')
273
274    page_adjust -- int (to add or subtract to the page number)
275    """
276
277    _tag = "text:page-number"
278    _properties = (
279        PropDef("select_page", "text:select-page"),
280        PropDef("page_adjust", "text:page-adjust"),
281    )
282
283    def __init__(
284        self,
285        select_page: str | None = None,
286        page_adjust: str | None = None,
287        **kwargs: Any,
288    ) -> None:
289        super().__init__(**kwargs)
290        if self._do_init:
291            if select_page is None:
292                select_page = "current"
293            self.select_page = select_page
294            if page_adjust is not None:
295                self.page_adjust = page_adjust

select_page -- string in ('previous', 'current', 'next')

page_adjust -- int (to add or subtract to the page number)

VarPageNumber( select_page: str | None = None, page_adjust: str | None = None, **kwargs: Any)
283    def __init__(
284        self,
285        select_page: str | None = None,
286        page_adjust: str | None = None,
287        **kwargs: Any,
288    ) -> None:
289        super().__init__(**kwargs)
290        if self._do_init:
291            if select_page is None:
292                select_page = "current"
293            self.select_page = select_page
294            if page_adjust is not None:
295                self.page_adjust = page_adjust
select_page: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
page_adjust: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarSet(odfdo.ElementTyped):
 64class VarSet(ElementTyped):
 65    _tag = "text:variable-set"
 66    _properties = (
 67        PropDef("name", "text:name"),
 68        PropDef("style", "style:data-style-name"),
 69        PropDef("display", "text:display"),
 70    )
 71
 72    def __init__(
 73        self,
 74        name: str | None = None,
 75        value: Any = None,
 76        value_type: str | None = None,
 77        display: str | bool = False,
 78        text: str | None = None,
 79        style: str | None = None,
 80        **kwargs: Any,
 81    ) -> None:
 82        super().__init__(**kwargs)
 83        if self._do_init:
 84            if name:
 85                self.name = name
 86            if style:
 87                self.style = style
 88            text = self.set_value_and_type(
 89                value=value, value_type=value_type, text=text
 90            )
 91            if not display:
 92                self.display = "none"
 93            else:
 94                self.text = text  # type: ignore
 95
 96    def set_value(self, value: Any) -> None:
 97        name = self.get_attribute("text:name")
 98        display = self.get_attribute("text:display")
 99        self.clear()
100        text = self.set_value_and_type(value=value)
101        self.set_attribute("text:name", name)
102        if display is not None:
103            self.set_attribute("text:display", display)
104        if isinstance(text, str):
105            self.text = text

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarSet( name: str | None = None, value: Any = None, value_type: str | None = None, display: str | bool = False, text: str | None = None, style: str | None = None, **kwargs: Any)
72    def __init__(
73        self,
74        name: str | None = None,
75        value: Any = None,
76        value_type: str | None = None,
77        display: str | bool = False,
78        text: str | None = None,
79        style: str | None = None,
80        **kwargs: Any,
81    ) -> None:
82        super().__init__(**kwargs)
83        if self._do_init:
84            if name:
85                self.name = name
86            if style:
87                self.style = style
88            text = self.set_value_and_type(
89                value=value, value_type=value_type, text=text
90            )
91            if not display:
92                self.display = "none"
93            else:
94                self.text = text  # type: ignore
def set_value(self, value: Any) -> None:
 96    def set_value(self, value: Any) -> None:
 97        name = self.get_attribute("text:name")
 98        display = self.get_attribute("text:display")
 99        self.clear()
100        text = self.set_value_and_type(value=value)
101        self.set_attribute("text:name", name)
102        if display is not None:
103            self.set_attribute("text:display", display)
104        if isinstance(text, str):
105            self.text = text
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
display: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarSubject(odfdo.VarInitialCreator):
518class VarSubject(VarInitialCreator):
519    _tag = "text:subject"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarTime(odfdo.Element):
341class VarTime(Element):
342    _tag = "text:time"
343    _properties = (
344        PropDef("time", "text:time-value"),
345        PropDef("fixed", "text:fixed"),
346        PropDef("data_style", "style:data-style-name"),
347        PropDef("time_adjust", "text:time-adjust"),
348    )
349
350    def __init__(
351        self,
352        time: datetime,
353        fixed: bool = False,
354        data_style: str | None = None,
355        text: str | None = None,
356        time_adjust: timedelta | None = None,
357        **kwargs: Any,
358    ) -> None:
359        super().__init__(**kwargs)
360        if self._do_init:
361            self.time = DateTime.encode(time)
362            if fixed:
363                self.fixed = True
364            if data_style is not None:
365                self.data_style = data_style
366            if text is None:
367                text = time.strftime("%H:%M:%S")
368            self.text = text
369            if time_adjust is not None:
370                self.date_adjust = Duration.encode(time_adjust)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarTime( time: datetime.datetime, fixed: bool = False, data_style: str | None = None, text: str | None = None, time_adjust: datetime.timedelta | None = None, **kwargs: Any)
350    def __init__(
351        self,
352        time: datetime,
353        fixed: bool = False,
354        data_style: str | None = None,
355        text: str | None = None,
356        time_adjust: timedelta | None = None,
357        **kwargs: Any,
358    ) -> None:
359        super().__init__(**kwargs)
360        if self._do_init:
361            self.time = DateTime.encode(time)
362            if fixed:
363                self.fixed = True
364            if data_style is not None:
365                self.data_style = data_style
366            if text is None:
367                text = time.strftime("%H:%M:%S")
368            self.text = text
369            if time_adjust is not None:
370                self.date_adjust = Duration.encode(time_adjust)
time: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
time_adjust: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarTitle(odfdo.VarInitialCreator):
511class VarTitle(VarInitialCreator):
512    _tag = "text:title"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class XmlPart:
 37class XmlPart:
 38    """Representation of an XML part.
 39
 40    Abstraction of the XML library behind.
 41    """
 42
 43    def __init__(self, part_name: str, container: Container) -> None:
 44        self.part_name = part_name
 45        self.container = container
 46
 47        # Internal state
 48        self.__tree: _ElementTree | None = None
 49        self.__root: Element | None = None
 50
 51    def _get_tree(self) -> _ElementTree:
 52        if self.__tree is None:
 53            part = self.container.get_part(self.part_name)
 54            self.__tree = parse(BytesIO(part))  # type: ignore
 55        return self.__tree
 56
 57    def __repr__(self) -> str:
 58        return f"<{self.__class__.__name__} part_name={self.part_name}>"
 59
 60    # Public API
 61
 62    @property
 63    def root(self) -> Element:
 64        if self.__root is None:
 65            tree = self._get_tree()
 66            self.__root = Element.from_tag(tree.getroot())
 67        return self.__root
 68
 69    def get_elements(self, xpath_query: str) -> list[Element | Text]:
 70        root = self.root
 71        return root.xpath(xpath_query)
 72
 73    def get_element(self, xpath_query: str) -> Any:
 74        result = self.get_elements(xpath_query)
 75        if not result:
 76            return None
 77        return result[0]
 78
 79    def delete_element(self, child: Element) -> None:
 80        child.delete()
 81
 82    def xpath(self, xpath_query: str) -> list[Element | Text]:
 83        """Apply XPath query to the XML part. Return list of Element or
 84        Text instances translated from the nodes found.
 85        """
 86        root = self.root
 87        return root.xpath(xpath_query)
 88
 89    @property
 90    def clone(self) -> XmlPart:
 91        clone = object.__new__(self.__class__)
 92        for name in self.__dict__:
 93            if name == "container":
 94                setattr(clone, name, self.container.clone)
 95            elif name in ("_XmlPart__tree",):
 96                setattr(clone, name, None)
 97            else:
 98                value = getattr(self, name)
 99                value = deepcopy(value)
100                setattr(clone, name, value)
101        return clone
102
103    def serialize(self, pretty: bool = False) -> bytes:
104        tree = self._get_tree()
105        # Lxml declaration is too exotic to me
106        data = [b'<?xml version="1.0" encoding="UTF-8"?>']
107        bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8")
108        # Lxml with pretty_print is adding a empty line
109        if pretty:
110            bytes_tree = bytes_tree.strip()
111        data.append(bytes_tree)
112        return b"\n".join(data)

Representation of an XML part.

Abstraction of the XML library behind.

XmlPart(part_name: str, container: Container)
43    def __init__(self, part_name: str, container: Container) -> None:
44        self.part_name = part_name
45        self.container = container
46
47        # Internal state
48        self.__tree: _ElementTree | None = None
49        self.__root: Element | None = None
part_name
container
root: Element
62    @property
63    def root(self) -> Element:
64        if self.__root is None:
65            tree = self._get_tree()
66            self.__root = Element.from_tag(tree.getroot())
67        return self.__root
def get_elements( self, xpath_query: str) -> list[Element | Text]:
69    def get_elements(self, xpath_query: str) -> list[Element | Text]:
70        root = self.root
71        return root.xpath(xpath_query)
def get_element(self, xpath_query: str) -> Any:
73    def get_element(self, xpath_query: str) -> Any:
74        result = self.get_elements(xpath_query)
75        if not result:
76            return None
77        return result[0]
def delete_element(self, child: Element) -> None:
79    def delete_element(self, child: Element) -> None:
80        child.delete()
def xpath( self, xpath_query: str) -> list[Element | Text]:
82    def xpath(self, xpath_query: str) -> list[Element | Text]:
83        """Apply XPath query to the XML part. Return list of Element or
84        Text instances translated from the nodes found.
85        """
86        root = self.root
87        return root.xpath(xpath_query)

Apply XPath query to the XML part. Return list of Element or Text instances translated from the nodes found.

clone: XmlPart
 89    @property
 90    def clone(self) -> XmlPart:
 91        clone = object.__new__(self.__class__)
 92        for name in self.__dict__:
 93            if name == "container":
 94                setattr(clone, name, self.container.clone)
 95            elif name in ("_XmlPart__tree",):
 96                setattr(clone, name, None)
 97            else:
 98                value = getattr(self, name)
 99                value = deepcopy(value)
100                setattr(clone, name, value)
101        return clone
def serialize(self, pretty: bool = False) -> bytes:
103    def serialize(self, pretty: bool = False) -> bytes:
104        tree = self._get_tree()
105        # Lxml declaration is too exotic to me
106        data = [b'<?xml version="1.0" encoding="UTF-8"?>']
107        bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8")
108        # Lxml with pretty_print is adding a empty line
109        if pretty:
110            bytes_tree = bytes_tree.strip()
111        data.append(bytes_tree)
112        return b"\n".join(data)
__version__ = '3.7.3'
def create_table_cell_style( border: str | None = None, border_top: str | None = None, border_bottom: str | None = None, border_left: str | None = None, border_right: str | None = None, padding: str | None = None, padding_top: str | None = None, padding_bottom: str | None = None, padding_left: str | None = None, padding_right: str | None = None, background_color: str | tuple | None = None, shadow: str | None = None, color: str | tuple | None = None) -> Style:
187def create_table_cell_style(
188    border: str | None = None,
189    border_top: str | None = None,
190    border_bottom: str | None = None,
191    border_left: str | None = None,
192    border_right: str | None = None,
193    padding: str | None = None,
194    padding_top: str | None = None,
195    padding_bottom: str | None = None,
196    padding_left: str | None = None,
197    padding_right: str | None = None,
198    background_color: str | tuple | None = None,
199    shadow: str | None = None,
200    color: str | tuple | None = None,
201) -> Style:
202    """Return a cell style.
203
204    The borders arguments must be some style attribute strings or None, see the
205    method 'make_table_cell_border_string' to generate them.
206    If the 'border' argument as the value 'default', the default style
207    "0.06pt solid #000000" is used for the 4 borders.
208    If any value is used for border, it is used for the 4 borders, else any of
209    the 4 borders can be specified by it's own string. If all the border,
210    border_top, border_bottom, ... arguments are None, an empty border is used
211    (ODF value is fo:border="none").
212
213    Padding arguments are string specifying a length (e.g. "0.5mm")". If
214    'padding' is provided, it is used for the 4 sides, else any of
215    the 4 sides padding can be specified by it's own string. Default padding is
216    no padding.
217
218    Arguments:
219
220        border -- str, style string for borders on four sides
221
222        border_top -- str, style string for top if no 'border' argument
223
224        border_bottom -- str, style string for bottom if no 'border' argument
225
226        border_left -- str, style string for left if no 'border' argument
227
228        border_right -- str, style string for right if no 'border' argument
229
230        padding -- str, style string for padding on four sides
231
232        padding_top -- str, style string for top if no 'padding' argument
233
234        padding_bottom -- str, style string for bottom if no 'padding' argument
235
236        padding_left -- str, style string for left if no 'padding' argument
237
238        padding_right -- str, style string for right if no 'padding' argument
239
240        background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
241
242        shadow -- str, e.g. "#808080 0.176cm 0.176cm"
243
244        color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
245
246    Return : Style
247    """
248    if border == "default":
249        border = make_table_cell_border_string()  # default border
250    if border is not None:
251        # use the border value for 4 sides.
252        border_bottom = border_top = border_left = border_right = None
253    if (
254        border is None
255        and border_bottom is None
256        and border_top is None
257        and border_left is None
258        and border_right is None
259    ):
260        border = "none"
261    if padding is not None:
262        # use the padding value for 4 sides.
263        padding_bottom = padding_top = padding_left = padding_right = None
264    cell_style = Style(
265        "table-cell",
266        area="table-cell",
267        border=border,
268        border_top=border_top,
269        border_bottom=border_bottom,
270        border_left=border_left,
271        border_right=border_right,
272        padding=padding,
273        padding_top=padding_top,
274        padding_bottom=padding_bottom,
275        padding_left=padding_left,
276        padding_right=padding_right,
277        background_color=background_color,
278        shadow=shadow,
279    )
280    if color:
281        cell_style.set_properties(area="text", color=color)
282    return cell_style

Return a cell style.

The borders arguments must be some style attribute strings or None, see the method 'make_table_cell_border_string' to generate them. If the 'border' argument as the value 'default', the default style "0.06pt solid #000000" is used for the 4 borders. If any value is used for border, it is used for the 4 borders, else any of the 4 borders can be specified by it's own string. If all the border, border_top, border_bottom, ... arguments are None, an empty border is used (ODF value is fo:border="none").

Padding arguments are string specifying a length (e.g. "0.5mm")". If 'padding' is provided, it is used for the 4 sides, else any of the 4 sides padding can be specified by it's own string. Default padding is no padding.

Arguments:

border -- str, style string for borders on four sides

border_top -- str, style string for top if no 'border' argument

border_bottom -- str, style string for bottom if no 'border' argument

border_left -- str, style string for left if no 'border' argument

border_right -- str, style string for right if no 'border' argument

padding -- str, style string for padding on four sides

padding_top -- str, style string for top if no 'padding' argument

padding_bottom -- str, style string for bottom if no 'padding' argument

padding_left -- str, style string for left if no 'padding' argument

padding_right -- str, style string for right if no 'padding' argument

background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

Return : Style

def default_boolean_style() -> Element:
1100def default_boolean_style() -> Element:
1101    return Element.from_tag(
1102        """<number:boolean-style style:name="lpod-default-boolean-style">
1103           <number:boolean/>
1104           </number:boolean-style>"""
1105    )
def default_currency_style() -> Element:
1108def default_currency_style() -> Element:
1109    return Element.from_tag(
1110        """<number:currency-style style:name="lpod-default-currency-style">
1111            <number:text>-</number:text>
1112            <number:number number:decimal-places="2"
1113             number:min-integer-digits="1"
1114             number:grouping="true"/>
1115            <number:text> </number:text>
1116            <number:currency-symbol
1117             number:language="fr"
1118             number:country="FR">€</number:currency-symbol>
1119           </number:currency-style>"""
1120    )
def default_date_style() -> Element:
1087def default_date_style() -> Element:
1088    return Element.from_tag(
1089        """
1090           <number:date-style style:name="lpod-default-date-style">
1091           <number:year number:style="long"/>
1092           <number:text>-</number:text>
1093           <number:month number:style="long"/>
1094           <number:text>-</number:text>
1095           <number:day number:style="long"/>
1096           </number:date-style>"""
1097    )
def default_frame_position_style( name: str = 'FramePosition', horizontal_pos: str = 'from-left', vertical_pos: str = 'from-top', horizontal_rel: str = 'paragraph', vertical_rel: str = 'paragraph') -> Style:
42def default_frame_position_style(
43    name: str = "FramePosition",
44    horizontal_pos: str = "from-left",
45    vertical_pos: str = "from-top",
46    horizontal_rel: str = "paragraph",
47    vertical_rel: str = "paragraph",
48) -> Style:
49    """Helper style for positioning frames in desktop applications that need
50    it.
51
52    Default arguments should be enough.
53
54    Use the returned Style as the frame style or build a new graphic style
55    with this style as the parent.
56    """
57    return Style(
58        family="graphic",
59        name=name,
60        horizontal_pos=horizontal_pos,
61        horizontal_rel=horizontal_rel,
62        vertical_pos=vertical_pos,
63        vertical_rel=vertical_rel,
64    )

Helper style for positioning frames in desktop applications that need it.

Default arguments should be enough.

Use the returned Style as the frame style or build a new graphic style with this style as the parent.

def default_number_style() -> Element:
1055def default_number_style() -> Element:
1056    return Element.from_tag(
1057        """<number:number-style style:name="lpod-default-number-style">
1058           <number:number number:decimal-places="2"
1059            number:min-integer-digits="1"/>
1060           </number:number-style>"""
1061    )
def default_percentage_style() -> Element:
1064def default_percentage_style() -> Element:
1065    return Element.from_tag(
1066        """<number:percentage-style
1067            style:name="lpod-default-percentage-style">
1068           <number:number number:decimal-places="2"
1069            number:min-integer-digits="1"/>
1070           <number:text>%</number:text>
1071           </number:percentage-style>"""
1072    )
def default_time_style() -> Element:
1075def default_time_style() -> Element:
1076    return Element.from_tag(
1077        """<number:time-style style:name="lpod-default-time-style">
1078           <number:hours number:style="long"/>
1079           <number:text>:</number:text>
1080           <number:minutes number:style="long"/>
1081           <number:text>:</number:text>
1082           <number:seconds number:style="long"/>
1083           </number:time-style>"""
1084    )
def default_toc_level_style(level: int) -> Style:
150def default_toc_level_style(level: int) -> Style:
151    """Generate an automatic default style for the given TOC level."""
152    tab_stop = TabStopStyle(style_type="right", leader_style="dotted", leader_text=".")
153    position = 17.5 - (0.5 * level)
154    tab_stop.style_position = f"{position}cm"
155    tab_stops = Element.from_tag("style:tab-stops")
156    tab_stops.append(tab_stop)
157    properties = Element.from_tag("style:paragraph-properties")
158    properties.append(tab_stops)
159    toc_style_level = Style(
160        family="paragraph",
161        name=_toc_entry_style_name(level),
162        parent=f"Contents_20_{level}",
163    )
164    toc_style_level.append(properties)
165    return toc_style_level

Generate an automatic default style for the given TOC level.

def hex2rgb(color: str) -> tuple[int, int, int]:
26def hex2rgb(color: str) -> tuple[int, int, int]:
27    """Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B)
28    tuple.
29
30    Arguments:
31
32        color -- str
33
34    Return: tuple
35    """
36    code = color[1:]
37    if not (len(color) == 7 and color[0] == "#" and code.isalnum()):
38        raise ValueError(f'"{color}" is not a valid color')
39    red = int(code[:2], 16)
40    green = int(code[2:4], 16)
41    blue = int(code[4:6], 16)
42    return (red, green, blue)

Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) tuple.

Arguments:

color -- str

Return: tuple

def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None:
 81def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None:
 82    """Convert a color definition of type tuple or string to hexadecimal
 83    representation.
 84
 85    Empty string is converted to black.
 86    None is converted to None.
 87
 88    Arguments:
 89
 90        color -- str or tuple or None
 91
 92    Return: str or None
 93    """
 94    if color is None:
 95        return None
 96    if isinstance(color, tuple):
 97        return rgb2hex(color)
 98    if not isinstance(color, str):
 99        raise TypeError(f'Invalid color argument "{color!r}"')
100    color = color.strip()
101    if not color:
102        return "#000000"
103    if color.startswith("#"):
104        return color
105    return rgb2hex(color)

Convert a color definition of type tuple or string to hexadecimal representation.

Empty string is converted to black. None is converted to None.

Arguments:

color -- str or tuple or None

Return: str or None

def make_table_cell_border_string( thick: str | float | int | None = None, line: str | None = None, color: str | tuple | None = None) -> str:
167def make_table_cell_border_string(
168    thick: str | float | int | None = None,
169    line: str | None = None,
170    color: str | tuple | None = None,
171) -> str:
172    """Returns a string for style:table-cell-properties fo:border,
173    with default : "0.06pt solid #000000"
174
175        thick -- str or float or int
176        line -- str
177        color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
178
179    Returns : str
180    """
181    thick_string = _make_thick_string(thick)
182    line_string = _make_line_string(line)
183    color_string = hexa_color(color) or "#000000"
184    return " ".join((thick_string, line_string, color_string))

Returns a string for style:table-cell-properties fo:border, with default : "0.06pt solid #000000"

thick -- str or float or int
line -- str
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

Returns : str

def rgb2hex(color: str | tuple[int, int, int]) -> str:
45def rgb2hex(color: str | tuple[int, int, int]) -> str:
46    """Turns a color name or a (R, G, B) color tuple into a "#RRGGBB"
47    hexadecimal representation.
48
49    Arguments:
50
51        color -- str or tuple
52
53    Return: str
54
55    Examples::
56
57        >>> rgb2hex('yellow')
58        '#FFFF00'
59        >>> rgb2hex((238, 130, 238))
60        '#EE82EE'
61    """
62    if isinstance(color, str):
63        try:
64            code = CSS3_COLORMAP[color.lower()]
65        except KeyError as e:
66            raise KeyError(f'Color "{color}" is unknown in CSS color list') from e
67    elif isinstance(color, tuple):
68        if len(color) != 3:
69            raise ValueError("Color must be a 3-tuple")
70        code = color
71    else:
72        raise TypeError(f'Invalid color "{color}"')
73    for channel in code:
74        if not 0 <= channel <= 255:
75            raise ValueError(
76                f'Invalid color "{color}", channel must be between 0 and 255'
77            )
78    return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"

Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" hexadecimal representation.

Arguments:

color -- str or tuple

Return: str

Examples::

>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'